This commit is contained in:
parent
e9fde15d5e
commit
52525afe72
140 changed files with 46348 additions and 1 deletions
1626
net-cli/src/main/java/org/jline/builtins/Commands.java
Normal file
1626
net-cli/src/main/java/org/jline/builtins/Commands.java
Normal file
File diff suppressed because it is too large
Load diff
1002
net-cli/src/main/java/org/jline/builtins/Completers.java
Normal file
1002
net-cli/src/main/java/org/jline/builtins/Completers.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2019, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.builtins;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class ConfigurationPath {
|
||||||
|
private final Path appConfig;
|
||||||
|
private final Path userConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration class constructor.
|
||||||
|
*
|
||||||
|
* @param appConfig Application configuration directory
|
||||||
|
* @param userConfig User private configuration directory
|
||||||
|
*/
|
||||||
|
public ConfigurationPath(Path appConfig, Path userConfig) {
|
||||||
|
this.appConfig = appConfig;
|
||||||
|
this.userConfig = userConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search configuration file first from userConfig and then appConfig directory. Returns null if file is not found.
|
||||||
|
*
|
||||||
|
* @param name Configuration file name.
|
||||||
|
* @return Configuration file.
|
||||||
|
*/
|
||||||
|
public Path getConfig(String name) {
|
||||||
|
Path out = null;
|
||||||
|
if (userConfig != null && userConfig.resolve(name).toFile().exists()) {
|
||||||
|
out = userConfig.resolve(name);
|
||||||
|
} else if (appConfig != null && appConfig.resolve(name).toFile().exists()) {
|
||||||
|
out = appConfig.resolve(name);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search configuration file from userConfig directory. Returns null if file is not found.
|
||||||
|
*
|
||||||
|
* @param name Configuration file name.
|
||||||
|
* @return Configuration file.
|
||||||
|
* @throws IOException When we do not have read access to the file or directory.
|
||||||
|
*/
|
||||||
|
public Path getUserConfig(String name) throws IOException {
|
||||||
|
return getUserConfig(name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search configuration file from userConfig directory. Returns null if file is not found.
|
||||||
|
*
|
||||||
|
* @param name Configuration file name
|
||||||
|
* @param create When true configuration file is created if not found.
|
||||||
|
* @return Configuration file.
|
||||||
|
* @throws IOException When we do not have read/write access to the file or directory.
|
||||||
|
*/
|
||||||
|
public Path getUserConfig(String name, boolean create) throws IOException {
|
||||||
|
Path out = null;
|
||||||
|
if (userConfig != null) {
|
||||||
|
if (!userConfig.resolve(name).toFile().exists() && create) {
|
||||||
|
userConfig.resolve(name).toFile().createNewFile();
|
||||||
|
}
|
||||||
|
if (userConfig.resolve(name).toFile().exists()) {
|
||||||
|
out = userConfig.resolve(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.builtins;
|
||||||
|
|
||||||
|
public interface ConsoleOptionGetter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return console option value
|
||||||
|
*
|
||||||
|
* @param name the option name
|
||||||
|
* @return option value
|
||||||
|
*/
|
||||||
|
Object consoleOption(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read console option value
|
||||||
|
*
|
||||||
|
* @param <T> option type
|
||||||
|
* @param option option name
|
||||||
|
* @param defval default value
|
||||||
|
* @return option value
|
||||||
|
*/
|
||||||
|
<T> T consoleOption(String option, T defval);
|
||||||
|
}
|
30
net-cli/src/main/java/org/jline/builtins/InputRC.java
Normal file
30
net-cli/src/main/java/org/jline/builtins/InputRC.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.builtins;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.URL;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
|
||||||
|
public final class InputRC {
|
||||||
|
|
||||||
|
public static void configure(LineReader reader, URL url) throws IOException {
|
||||||
|
org.jline.reader.impl.InputRC.configure(reader, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void configure(LineReader reader, InputStream is) throws IOException {
|
||||||
|
org.jline.reader.impl.InputRC.configure(reader, is);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void configure(LineReader reader, Reader r) throws IOException {
|
||||||
|
org.jline.reader.impl.InputRC.configure(reader, r);
|
||||||
|
}
|
||||||
|
}
|
1587
net-cli/src/main/java/org/jline/builtins/Less.java
Normal file
1587
net-cli/src/main/java/org/jline/builtins/Less.java
Normal file
File diff suppressed because it is too large
Load diff
292
net-cli/src/main/java/org/jline/builtins/NfaMatcher.java
Normal file
292
net-cli/src/main/java/org/jline/builtins/NfaMatcher.java
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.builtins;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NFA implementation.
|
||||||
|
* See https://swtch.com/~rsc/regexp/regexp1.html
|
||||||
|
*/
|
||||||
|
public class NfaMatcher<T> {
|
||||||
|
|
||||||
|
private final String regexp;
|
||||||
|
private final BiFunction<T, String, Boolean> matcher;
|
||||||
|
private volatile State start;
|
||||||
|
|
||||||
|
public NfaMatcher(String regexp, BiFunction<T, String, Boolean> matcher) {
|
||||||
|
this.regexp = regexp;
|
||||||
|
this.matcher = matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
static State toNfa(List<String> postfix) {
|
||||||
|
Deque<Frag> stack = new ArrayDeque<>();
|
||||||
|
Frag e1, e2, e;
|
||||||
|
State s;
|
||||||
|
for (String p : postfix) {
|
||||||
|
switch (p) {
|
||||||
|
case ".":
|
||||||
|
e2 = stack.pollLast();
|
||||||
|
e1 = stack.pollLast();
|
||||||
|
e1.patch(e2.start);
|
||||||
|
stack.offerLast(new Frag(e1.start, e2.out));
|
||||||
|
break;
|
||||||
|
case "|":
|
||||||
|
e2 = stack.pollLast();
|
||||||
|
e1 = stack.pollLast();
|
||||||
|
s = new State(State.Split, e1.start, e2.start);
|
||||||
|
stack.offerLast(new Frag(s, e1.out, e2.out));
|
||||||
|
break;
|
||||||
|
case "?":
|
||||||
|
e = stack.pollLast();
|
||||||
|
s = new State(State.Split, e.start, null);
|
||||||
|
stack.offerLast(new Frag(s, e.out, s::setOut1));
|
||||||
|
break;
|
||||||
|
case "*":
|
||||||
|
e = stack.pollLast();
|
||||||
|
s = new State(State.Split, e.start, null);
|
||||||
|
e.patch(s);
|
||||||
|
stack.offerLast(new Frag(s, s::setOut1));
|
||||||
|
break;
|
||||||
|
case "+":
|
||||||
|
e = stack.pollLast();
|
||||||
|
s = new State(State.Split, e.start, null);
|
||||||
|
e.patch(s);
|
||||||
|
stack.offerLast(new Frag(e.start, s::setOut1));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
s = new State(p, null, null);
|
||||||
|
stack.offerLast(new Frag(s, s::setOut));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e = stack.pollLast();
|
||||||
|
if (!stack.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Wrong postfix expression, " + stack.size() + " elements remaining");
|
||||||
|
}
|
||||||
|
e.patch(new State(State.Match, null, null));
|
||||||
|
return e.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> toPostFix(String regexp) {
|
||||||
|
List<String> postfix = new ArrayList<>();
|
||||||
|
int s = -1;
|
||||||
|
int natom = 0;
|
||||||
|
int nalt = 0;
|
||||||
|
Deque<Integer> natoms = new ArrayDeque<>();
|
||||||
|
Deque<Integer> nalts = new ArrayDeque<>();
|
||||||
|
for (int i = 0; i < regexp.length(); i++) {
|
||||||
|
char c = regexp.charAt(i);
|
||||||
|
// Scan identifiers
|
||||||
|
if (Character.isJavaIdentifierPart(c)) {
|
||||||
|
if (s < 0) {
|
||||||
|
s = i;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// End of identifier
|
||||||
|
if (s >= 0) {
|
||||||
|
if (natom > 1) {
|
||||||
|
--natom;
|
||||||
|
postfix.add(".");
|
||||||
|
}
|
||||||
|
postfix.add(regexp.substring(s, i));
|
||||||
|
natom++;
|
||||||
|
s = -1;
|
||||||
|
}
|
||||||
|
// Ignore space
|
||||||
|
if (Character.isWhitespace(c)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Special characters
|
||||||
|
switch (c) {
|
||||||
|
case '(':
|
||||||
|
if (natom > 1) {
|
||||||
|
--natom;
|
||||||
|
postfix.add(".");
|
||||||
|
}
|
||||||
|
nalts.offerLast(nalt);
|
||||||
|
natoms.offerLast(natom);
|
||||||
|
nalt = 0;
|
||||||
|
natom = 0;
|
||||||
|
break;
|
||||||
|
case '|':
|
||||||
|
if (natom == 0) {
|
||||||
|
throw new IllegalStateException("unexpected '" + c + "' at pos " + i);
|
||||||
|
}
|
||||||
|
while (--natom > 0) {
|
||||||
|
postfix.add(".");
|
||||||
|
}
|
||||||
|
nalt++;
|
||||||
|
break;
|
||||||
|
case ')':
|
||||||
|
if (nalts.isEmpty() || natom == 0) {
|
||||||
|
throw new IllegalStateException("unexpected '" + c + "' at pos " + i);
|
||||||
|
}
|
||||||
|
while (--natom > 0) {
|
||||||
|
postfix.add(".");
|
||||||
|
}
|
||||||
|
for (; nalt > 0; nalt--) {
|
||||||
|
postfix.add("|");
|
||||||
|
}
|
||||||
|
nalt = nalts.pollLast();
|
||||||
|
natom = natoms.pollLast();
|
||||||
|
natom++;
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
case '+':
|
||||||
|
case '?':
|
||||||
|
if (natom == 0) {
|
||||||
|
throw new IllegalStateException("unexpected '" + c + "' at pos " + i);
|
||||||
|
}
|
||||||
|
postfix.add(String.valueOf(c));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("unexpected '" + c + "' at pos " + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// End of identifier
|
||||||
|
if (s >= 0) {
|
||||||
|
if (natom > 1) {
|
||||||
|
--natom;
|
||||||
|
postfix.add(".");
|
||||||
|
}
|
||||||
|
postfix.add(regexp.substring(s));
|
||||||
|
natom++;
|
||||||
|
}
|
||||||
|
// Append
|
||||||
|
while (--natom > 0) {
|
||||||
|
postfix.add(".");
|
||||||
|
}
|
||||||
|
// Alternatives
|
||||||
|
for (; nalt > 0; nalt--) {
|
||||||
|
postfix.add("|");
|
||||||
|
}
|
||||||
|
return postfix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compile() {
|
||||||
|
if (start == null) {
|
||||||
|
start = toNfa(toPostFix(regexp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean match(List<T> args) {
|
||||||
|
Set<State> clist = new HashSet<>();
|
||||||
|
compile();
|
||||||
|
addState(clist, start);
|
||||||
|
for (T arg : args) {
|
||||||
|
Set<State> nlist = new HashSet<>();
|
||||||
|
clist.stream()
|
||||||
|
.filter(s -> !Objects.equals(State.Match, s.c) && !Objects.equals(State.Split, s.c))
|
||||||
|
.filter(s -> matcher.apply(arg, s.c))
|
||||||
|
.forEach(s -> addState(nlist, s.out));
|
||||||
|
clist = nlist;
|
||||||
|
}
|
||||||
|
return clist.stream().anyMatch(s -> Objects.equals(State.Match, s.c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of possible matcher names for the next object
|
||||||
|
*
|
||||||
|
* @param args input list
|
||||||
|
* @return the list of possible matcher names for the next object
|
||||||
|
*/
|
||||||
|
public Set<String> matchPartial(List<T> args) {
|
||||||
|
Set<State> clist = new HashSet<>();
|
||||||
|
compile();
|
||||||
|
addState(clist, start);
|
||||||
|
for (T arg : args) {
|
||||||
|
Set<State> nlist = new HashSet<>();
|
||||||
|
clist.stream()
|
||||||
|
.filter(s -> !Objects.equals(State.Match, s.c) && !Objects.equals(State.Split, s.c))
|
||||||
|
.filter(s -> matcher.apply(arg, s.c))
|
||||||
|
.forEach(s -> addState(nlist, s.out));
|
||||||
|
clist = nlist;
|
||||||
|
}
|
||||||
|
return clist.stream()
|
||||||
|
.filter(s -> !Objects.equals(State.Match, s.c) && !Objects.equals(State.Split, s.c))
|
||||||
|
.map(s -> s.c)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
void addState(Set<State> l, State s) {
|
||||||
|
if (s != null && l.add(s)) {
|
||||||
|
if (Objects.equals(State.Split, s.c)) {
|
||||||
|
addState(l, s.out);
|
||||||
|
addState(l, s.out1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class State {
|
||||||
|
|
||||||
|
static final String Match = "++MATCH++";
|
||||||
|
static final String Split = "++SPLIT++";
|
||||||
|
|
||||||
|
final String c;
|
||||||
|
State out;
|
||||||
|
State out1;
|
||||||
|
|
||||||
|
public State(String c, State out, State out1) {
|
||||||
|
this.c = c;
|
||||||
|
this.out = out;
|
||||||
|
this.out1 = out1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOut(State out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOut1(State out1) {
|
||||||
|
this.out1 = out1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Frag {
|
||||||
|
final State start;
|
||||||
|
final List<Consumer<State>> out = new ArrayList<>();
|
||||||
|
|
||||||
|
public Frag(State start, Collection<Consumer<State>> l) {
|
||||||
|
this.start = start;
|
||||||
|
this.out.addAll(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Frag(State start, Collection<Consumer<State>> l1, Collection<Consumer<State>> l2) {
|
||||||
|
this.start = start;
|
||||||
|
this.out.addAll(l1);
|
||||||
|
this.out.addAll(l2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Frag(State start, Consumer<State> c) {
|
||||||
|
this.start = start;
|
||||||
|
this.out.add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Frag(State start, Collection<Consumer<State>> l, Consumer<State> c) {
|
||||||
|
this.start = start;
|
||||||
|
this.out.addAll(l);
|
||||||
|
this.out.add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void patch(State s) {
|
||||||
|
out.forEach(c -> c.accept(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
588
net-cli/src/main/java/org/jline/builtins/Options.java
Normal file
588
net-cli/src/main/java/org/jline/builtins/Options.java
Normal file
|
@ -0,0 +1,588 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.builtins;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
import org.jline.utils.AttributedStringBuilder;
|
||||||
|
import org.jline.utils.StyleResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yet another GNU long options parser. This one is configured by parsing its Usage string.
|
||||||
|
* <p>
|
||||||
|
* Code coming from Apache Felix Gogo Shell
|
||||||
|
*/
|
||||||
|
public class Options {
|
||||||
|
|
||||||
|
public static final String NL = System.getProperty("line.separator", "\n");
|
||||||
|
|
||||||
|
// Note: need to double \ within ""
|
||||||
|
private static final 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
|
||||||
|
|
||||||
|
private static final int GROUP_SHORT_OPT_1 = 1;
|
||||||
|
private static final int GROUP_SHORT_OPT_2 = 2;
|
||||||
|
private static final int GROUP_LONG_OPT_1 = 3;
|
||||||
|
private static final int GROUP_ARG_1 = 4;
|
||||||
|
private static final int GROUP_LONG_OPT_2 = 5;
|
||||||
|
private static final int GROUP_DEFAULT = 6;
|
||||||
|
|
||||||
|
private static final Pattern parser = Pattern.compile(regex);
|
||||||
|
private static final Pattern uname = Pattern.compile("^Usage:\\s+(\\w+)");
|
||||||
|
private static final String UNKNOWN = "unknown";
|
||||||
|
private final Map<String, Boolean> unmodifiableOptSet;
|
||||||
|
private final Map<String, Object> unmodifiableOptArg;
|
||||||
|
private final Map<String, Boolean> optSet = new HashMap<>();
|
||||||
|
private final Map<String, Object> optArg = new HashMap<>();
|
||||||
|
private final Map<String, String> optName = new HashMap<>();
|
||||||
|
private final Map<String, String> optAlias = new HashMap<>();
|
||||||
|
private final List<Object> xargs = new ArrayList<>();
|
||||||
|
private final String[] spec;
|
||||||
|
private final String[] gspec;
|
||||||
|
private final String defOpts;
|
||||||
|
private final String[] defArgs;
|
||||||
|
private List<String> args = null;
|
||||||
|
private String usageName = UNKNOWN;
|
||||||
|
private int usageIndex = 0;
|
||||||
|
private String error = null;
|
||||||
|
|
||||||
|
private boolean optionsFirst = false;
|
||||||
|
private boolean stopOnBadOption = false;
|
||||||
|
|
||||||
|
// internal constructor
|
||||||
|
private Options(String[] spec, String[] gspec, Options opt, Function<String, String> env) {
|
||||||
|
this.gspec = gspec;
|
||||||
|
|
||||||
|
if (gspec == null && opt == null) {
|
||||||
|
this.spec = spec;
|
||||||
|
} else {
|
||||||
|
ArrayList<String> list = new ArrayList<>();
|
||||||
|
list.addAll(Arrays.asList(spec));
|
||||||
|
list.addAll(Arrays.asList(gspec != null ? gspec : opt.gspec));
|
||||||
|
this.spec = list.toArray(new String[list.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Boolean> myOptSet = new HashMap<>();
|
||||||
|
Map<String, Object> myOptArg = new HashMap<>();
|
||||||
|
|
||||||
|
parseSpec(myOptSet, myOptArg);
|
||||||
|
|
||||||
|
if (opt != null) {
|
||||||
|
for (Entry<String, Boolean> e : opt.optSet.entrySet()) {
|
||||||
|
if (e.getValue()) myOptSet.put(e.getKey(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Entry<String, Object> e : opt.optArg.entrySet()) {
|
||||||
|
if (!e.getValue().equals("")) myOptArg.put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
opt.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
unmodifiableOptSet = Collections.unmodifiableMap(myOptSet);
|
||||||
|
unmodifiableOptArg = Collections.unmodifiableMap(myOptArg);
|
||||||
|
|
||||||
|
defOpts = env != null ? env.apply(usageName.toUpperCase() + "_OPTS") : null;
|
||||||
|
defArgs = (defOpts != null) ? defOpts.split("\\s+") : new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Options compile(String[] optSpec) {
|
||||||
|
return new Options(optSpec, null, null, System::getenv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Options compile(String[] optSpec, Function<String, String> env) {
|
||||||
|
return new Options(optSpec, null, null, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Options compile(String optSpec) {
|
||||||
|
return compile(optSpec.split("\\n"), System::getenv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Options compile(String optSpec, Function<String, String> env) {
|
||||||
|
return compile(optSpec.split("\\n"), env);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Options compile(String[] optSpec, Options gopt) {
|
||||||
|
return new Options(optSpec, null, gopt, System::getenv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Options compile(String[] optSpec, String[] gspec) {
|
||||||
|
return new Options(optSpec, gspec, null, System::getenv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options setStopOnBadOption(boolean stopOnBadOption) {
|
||||||
|
this.stopOnBadOption = stopOnBadOption;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options setOptionsFirst(boolean optionsFirst) {
|
||||||
|
this.optionsFirst = optionsFirst;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSet(String name) {
|
||||||
|
Boolean isSet = optSet.get(name);
|
||||||
|
if (isSet == null) {
|
||||||
|
throw new IllegalArgumentException("option not defined in spec: " + name);
|
||||||
|
}
|
||||||
|
return isSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getObject(String name) {
|
||||||
|
if (!optArg.containsKey(name)) throw new IllegalArgumentException("option not defined with argument: " + name);
|
||||||
|
|
||||||
|
List<Object> list = getObjectList(name);
|
||||||
|
|
||||||
|
return list.isEmpty() ? "" : list.get(list.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<Object> getObjectList(String name) {
|
||||||
|
List<Object> list;
|
||||||
|
Object arg = optArg.get(name);
|
||||||
|
|
||||||
|
if (arg == null) {
|
||||||
|
throw new IllegalArgumentException("option not defined with argument: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg instanceof String) { // default value
|
||||||
|
list = new ArrayList<>();
|
||||||
|
if (!"".equals(arg)) list.add(arg);
|
||||||
|
} else {
|
||||||
|
list = (List<Object>) arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getList(String name) {
|
||||||
|
ArrayList<String> list = new ArrayList<>();
|
||||||
|
for (Object o : getObjectList(name)) {
|
||||||
|
try {
|
||||||
|
list.add((String) o);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new IllegalArgumentException("option not String: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void addArg(String name, Object value) {
|
||||||
|
List<Object> list;
|
||||||
|
Object arg = optArg.get(name);
|
||||||
|
|
||||||
|
if (arg instanceof String) { // default value
|
||||||
|
list = new ArrayList<>();
|
||||||
|
optArg.put(name, list);
|
||||||
|
} else {
|
||||||
|
list = (List<Object>) arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String name) {
|
||||||
|
try {
|
||||||
|
return (String) getObject(name);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new IllegalArgumentException("option not String: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumber(String name) {
|
||||||
|
String number = get(name);
|
||||||
|
try {
|
||||||
|
if (number != null) return Integer.parseInt(number);
|
||||||
|
return 0;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("option '" + name + "' not Number: " + number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> argObjects() {
|
||||||
|
return xargs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> args() {
|
||||||
|
if (args == null) {
|
||||||
|
args = new ArrayList<>();
|
||||||
|
for (Object arg : xargs) {
|
||||||
|
args.add(arg == null ? "null" : arg.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Added for backword compability
|
||||||
|
public void usage(PrintStream err) {
|
||||||
|
err.print(usage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String usage() {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
buf.append(error);
|
||||||
|
buf.append(NL);
|
||||||
|
index = usageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = index; i < spec.length; ++i) {
|
||||||
|
buf.append(spec[i]);
|
||||||
|
buf.append(NL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints usage message and returns IllegalArgumentException, for you to throw.
|
||||||
|
*
|
||||||
|
* @param s the message to display
|
||||||
|
* @return an exception with the generated message
|
||||||
|
*/
|
||||||
|
public IllegalArgumentException usageError(String s) {
|
||||||
|
error = usageName + ": " + s;
|
||||||
|
return new IllegalArgumentException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse option spec.
|
||||||
|
*/
|
||||||
|
private void parseSpec(Map<String, Boolean> myOptSet, Map<String, Object> myOptArg) {
|
||||||
|
int index = 0;
|
||||||
|
for (String line : spec) {
|
||||||
|
Matcher m = parser.matcher(line);
|
||||||
|
|
||||||
|
if (m.matches()) {
|
||||||
|
final String opt = m.group(GROUP_LONG_OPT_1);
|
||||||
|
final String name = (opt != null) ? opt : m.group(GROUP_SHORT_OPT_1);
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
if (myOptSet.putIfAbsent(name, false) != null)
|
||||||
|
throw new IllegalArgumentException("duplicate option in spec: --" + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
String dflt = (m.group(GROUP_DEFAULT) != null) ? m.group(GROUP_DEFAULT) : "";
|
||||||
|
if (m.group(GROUP_ARG_1) != null) myOptArg.put(opt, dflt);
|
||||||
|
|
||||||
|
String opt2 = m.group(GROUP_LONG_OPT_2);
|
||||||
|
if (opt2 != null) {
|
||||||
|
optAlias.put(opt2, opt);
|
||||||
|
myOptSet.put(opt2, false);
|
||||||
|
if (m.group(GROUP_ARG_1) != null) myOptArg.put(opt2, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
String sopt = m.group(i == 0 ? GROUP_SHORT_OPT_1 : GROUP_SHORT_OPT_2);
|
||||||
|
if (sopt != null) {
|
||||||
|
if (optName.putIfAbsent(sopt, name) != null)
|
||||||
|
throw new IllegalArgumentException("duplicate option in spec: -" + sopt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Objects.equals(usageName, UNKNOWN)) {
|
||||||
|
Matcher u = uname.matcher(line);
|
||||||
|
if (u.find()) {
|
||||||
|
usageName = u.group(1);
|
||||||
|
usageIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
optSet.clear();
|
||||||
|
optSet.putAll(unmodifiableOptSet);
|
||||||
|
optArg.clear();
|
||||||
|
optArg.putAll(unmodifiableOptArg);
|
||||||
|
xargs.clear();
|
||||||
|
args = null;
|
||||||
|
error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options parse(Object[] argv) {
|
||||||
|
return parse(argv, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options parse(List<?> argv) {
|
||||||
|
return parse(argv, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options parse(Object[] argv, boolean skipArg0) {
|
||||||
|
if (null == argv) throw new IllegalArgumentException("argv is null");
|
||||||
|
|
||||||
|
return parse(Arrays.asList(argv), skipArg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options parse(List<?> argv, boolean skipArg0) {
|
||||||
|
reset();
|
||||||
|
List<Object> args = new ArrayList<>();
|
||||||
|
args.addAll(Arrays.asList(defArgs));
|
||||||
|
|
||||||
|
for (Object arg : argv) {
|
||||||
|
if (skipArg0) {
|
||||||
|
skipArg0 = false;
|
||||||
|
usageName = arg.toString();
|
||||||
|
} else {
|
||||||
|
args.add(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String needArg = null;
|
||||||
|
String needOpt = null;
|
||||||
|
boolean endOpt = false;
|
||||||
|
|
||||||
|
for (Object oarg : args) {
|
||||||
|
String arg = oarg == null ? "null" : oarg.toString();
|
||||||
|
|
||||||
|
if (endOpt) {
|
||||||
|
xargs.add(oarg);
|
||||||
|
} else if (needArg != null) {
|
||||||
|
addArg(needArg, oarg);
|
||||||
|
needArg = null;
|
||||||
|
needOpt = null;
|
||||||
|
} else if (!arg.startsWith("-")
|
||||||
|
|| (arg.length() > 1 && Character.isDigit(arg.charAt(1)))
|
||||||
|
|| "-".equals(oarg)) {
|
||||||
|
if (optionsFirst) endOpt = true;
|
||||||
|
xargs.add(oarg);
|
||||||
|
} else {
|
||||||
|
if (arg.equals("--")) endOpt = true;
|
||||||
|
else if (arg.startsWith("--")) {
|
||||||
|
int eq = arg.indexOf("=");
|
||||||
|
String value = (eq == -1) ? null : arg.substring(eq + 1);
|
||||||
|
String name = arg.substring(2, ((eq == -1) ? arg.length() : eq));
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
|
||||||
|
if (optSet.containsKey(name)) {
|
||||||
|
names.add(name);
|
||||||
|
} else {
|
||||||
|
for (String k : optSet.keySet()) {
|
||||||
|
if (k.startsWith(name)) names.add(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (names.size()) {
|
||||||
|
case 1:
|
||||||
|
name = names.get(0);
|
||||||
|
optSet.put(name, true);
|
||||||
|
if (optArg.containsKey(name)) {
|
||||||
|
if (value != null) addArg(name, value);
|
||||||
|
else needArg = name;
|
||||||
|
} else if (value != null) {
|
||||||
|
throw usageError("option '--" + name + "' doesn't allow an argument");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
if (stopOnBadOption) {
|
||||||
|
endOpt = true;
|
||||||
|
xargs.add(oarg);
|
||||||
|
break;
|
||||||
|
} else throw usageError("invalid option '--" + name + "'");
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw usageError("option '--" + name + "' is ambiguous: " + names);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 1; i < arg.length(); i++) {
|
||||||
|
String c = String.valueOf(arg.charAt(i));
|
||||||
|
if (optName.containsKey(c)) {
|
||||||
|
String name = optName.get(c);
|
||||||
|
optSet.put(name, true);
|
||||||
|
if (optArg.containsKey(name)) {
|
||||||
|
int k = i + 1;
|
||||||
|
if (k < arg.length()) {
|
||||||
|
addArg(name, arg.substring(k));
|
||||||
|
} else {
|
||||||
|
needOpt = c;
|
||||||
|
needArg = name;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (stopOnBadOption) {
|
||||||
|
xargs.add("-" + c);
|
||||||
|
endOpt = true;
|
||||||
|
} else throw usageError("invalid option '" + c + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needArg != null) {
|
||||||
|
String name = (needOpt != null) ? needOpt : "--" + needArg;
|
||||||
|
throw usageError("option '" + name + "' requires an argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove long option aliases
|
||||||
|
for (Entry<String, String> alias : optAlias.entrySet()) {
|
||||||
|
if (optSet.get(alias.getKey())) {
|
||||||
|
optSet.put(alias.getValue(), true);
|
||||||
|
if (optArg.containsKey(alias.getKey())) optArg.put(alias.getValue(), optArg.get(alias.getKey()));
|
||||||
|
}
|
||||||
|
optSet.remove(alias.getKey());
|
||||||
|
optArg.remove(alias.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "isSet" + optSet + "\nArg" + optArg + "\nargs" + xargs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when using the <code>--help</code> option on a built-in command.
|
||||||
|
* It can be highlighted using the {@link #highlight(String, StyleResolver)} method and then printed
|
||||||
|
* to the {@link org.jline.terminal.Terminal}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public static class HelpException extends Exception {
|
||||||
|
|
||||||
|
public HelpException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StyleResolver defaultStyle() {
|
||||||
|
return Styles.helpStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AttributedString highlight(String msg, StyleResolver resolver) {
|
||||||
|
Matcher tm = Pattern.compile("(^|\\n)(Usage|Summary)(:)").matcher(msg);
|
||||||
|
if (tm.find()) {
|
||||||
|
boolean subcommand = tm.group(2).equals("Summary");
|
||||||
|
AttributedStringBuilder asb = new AttributedStringBuilder(msg.length());
|
||||||
|
// Command
|
||||||
|
AttributedStringBuilder acommand = new AttributedStringBuilder()
|
||||||
|
.append(msg.substring(0, tm.start(2)))
|
||||||
|
.styleMatches(
|
||||||
|
Pattern.compile("(?:^\\s*)([a-z]+[a-zA-Z0-9-]*)\\b"),
|
||||||
|
Collections.singletonList(resolver.resolve(".co")));
|
||||||
|
asb.append(acommand);
|
||||||
|
// Title
|
||||||
|
asb.styled(resolver.resolve(".ti"), tm.group(2)).append(":");
|
||||||
|
// Syntax
|
||||||
|
for (String line : msg.substring(tm.end(3)).split("\n")) {
|
||||||
|
int ind = line.lastIndexOf(" ");
|
||||||
|
String syntax, comment;
|
||||||
|
if (ind > 20) {
|
||||||
|
syntax = line.substring(0, ind);
|
||||||
|
comment = line.substring(ind + 1);
|
||||||
|
} else {
|
||||||
|
syntax = line;
|
||||||
|
comment = "";
|
||||||
|
}
|
||||||
|
asb.append(_highlightSyntax(syntax, resolver, subcommand));
|
||||||
|
asb.append(_highlightComment(comment, resolver));
|
||||||
|
asb.append("\n");
|
||||||
|
}
|
||||||
|
return asb.toAttributedString();
|
||||||
|
} else {
|
||||||
|
return AttributedString.fromAnsi(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AttributedString highlightSyntax(String syntax, StyleResolver resolver, boolean subcommands) {
|
||||||
|
return _highlightSyntax(syntax, resolver, subcommands).toAttributedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AttributedString highlightSyntax(String syntax, StyleResolver resolver) {
|
||||||
|
return _highlightSyntax(syntax, resolver, false).toAttributedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AttributedString highlightComment(String comment, StyleResolver resolver) {
|
||||||
|
return _highlightComment(comment, resolver).toAttributedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AttributedStringBuilder _highlightSyntax(
|
||||||
|
String syntax, StyleResolver resolver, boolean subcommand) {
|
||||||
|
StringBuilder indent = new StringBuilder();
|
||||||
|
for (char c : syntax.toCharArray()) {
|
||||||
|
if (c != ' ') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
indent.append(c);
|
||||||
|
}
|
||||||
|
AttributedStringBuilder asyntax = new AttributedStringBuilder().append(syntax.substring(indent.length()));
|
||||||
|
// command
|
||||||
|
asyntax.styleMatches(
|
||||||
|
Pattern.compile("(?:^)([a-z]+[a-zA-Z0-9-]*)\\b"),
|
||||||
|
Collections.singletonList(resolver.resolve(".co")));
|
||||||
|
if (!subcommand) {
|
||||||
|
// argument
|
||||||
|
asyntax.styleMatches(
|
||||||
|
Pattern.compile("(?:<|\\[|\\s|=)([A-Za-z]+[A-Za-z_-]*)\\b"),
|
||||||
|
Collections.singletonList(resolver.resolve(".ar")));
|
||||||
|
// option
|
||||||
|
asyntax.styleMatches(
|
||||||
|
Pattern.compile("(?:^|\\s|\\[)(-\\$|-\\?|[-]{1,2}[A-Za-z-]+\\b)"),
|
||||||
|
Collections.singletonList(resolver.resolve(".op")));
|
||||||
|
}
|
||||||
|
return new AttributedStringBuilder().append(indent).append(asyntax);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AttributedStringBuilder _highlightComment(String comment, StyleResolver resolver) {
|
||||||
|
AttributedStringBuilder acomment = new AttributedStringBuilder().append(comment);
|
||||||
|
// option
|
||||||
|
acomment.styleMatches(
|
||||||
|
Pattern.compile("(?:\\s|\\[)(-\\$|-\\?|[-]{1,2}[A-Za-z-]+\\b)"),
|
||||||
|
Collections.singletonList(resolver.resolve(".op")));
|
||||||
|
// argument in comment
|
||||||
|
acomment.styleMatches(
|
||||||
|
Pattern.compile("(?:\\s)([a-z]+[-]+[a-z]+|[A-Z_]{2,})(?:\\s)"),
|
||||||
|
Collections.singletonList(resolver.resolve(".ar")));
|
||||||
|
return acomment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1976
net-cli/src/main/java/org/jline/builtins/ScreenTerminal.java
Normal file
1976
net-cli/src/main/java/org/jline/builtins/ScreenTerminal.java
Normal file
File diff suppressed because it is too large
Load diff
172
net-cli/src/main/java/org/jline/builtins/Source.java
Normal file
172
net-cli/src/main/java/org/jline/builtins/Source.java
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2022, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.builtins;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public interface Source {
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
InputStream read() throws IOException;
|
||||||
|
|
||||||
|
Long lines();
|
||||||
|
|
||||||
|
class URLSource implements Source {
|
||||||
|
final URL url;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
public URLSource(URL url, String name) {
|
||||||
|
this.url = Objects.requireNonNull(url);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream read() throws IOException {
|
||||||
|
return url.openStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long lines() {
|
||||||
|
Long out = null;
|
||||||
|
try (Stream<String> lines = Files.lines(new File(url.toURI()).toPath())) {
|
||||||
|
out = lines.count();
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathSource implements Source {
|
||||||
|
final Path path;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
public PathSource(File file, String name) {
|
||||||
|
this(Objects.requireNonNull(file).toPath(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PathSource(Path path, String name) {
|
||||||
|
this.path = Objects.requireNonNull(path);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream read() throws IOException {
|
||||||
|
return Files.newInputStream(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long lines() {
|
||||||
|
Long out = null;
|
||||||
|
try (Stream<String> lines = Files.lines(path)) {
|
||||||
|
out = lines.count();
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InputStreamSource implements Source {
|
||||||
|
final InputStream in;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
public InputStreamSource(InputStream in, boolean close, String name) {
|
||||||
|
Objects.requireNonNull(in);
|
||||||
|
if (close) {
|
||||||
|
this.in = in;
|
||||||
|
} else {
|
||||||
|
this.in = new FilterInputStream(in) {
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (this.in.markSupported()) {
|
||||||
|
this.in.mark(Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream read() throws IOException {
|
||||||
|
if (in.markSupported()) {
|
||||||
|
in.reset();
|
||||||
|
}
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long lines() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StdInSource extends InputStreamSource {
|
||||||
|
|
||||||
|
public StdInSource() {
|
||||||
|
this(System.in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StdInSource(InputStream in) {
|
||||||
|
super(in, false, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResourceSource implements Source {
|
||||||
|
final String resource;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
public ResourceSource(String resource) {
|
||||||
|
this(resource, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceSource(String resource, String name) {
|
||||||
|
this.resource = Objects.requireNonNull(resource);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream read() throws IOException {
|
||||||
|
return getClass().getResourceAsStream(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long lines() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
221
net-cli/src/main/java/org/jline/builtins/Styles.java
Normal file
221
net-cli/src/main/java/org/jline/builtins/Styles.java
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.builtins;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.jline.utils.StyleResolver;
|
||||||
|
import static org.jline.builtins.SyntaxHighlighter.REGEX_TOKEN_NAME;
|
||||||
|
|
||||||
|
public class Styles {
|
||||||
|
public static final String NANORC_THEME = "NANORC_THEME";
|
||||||
|
protected static final List<String> ANSI_STYLES = Arrays.asList(
|
||||||
|
"blink",
|
||||||
|
"bold",
|
||||||
|
"conceal",
|
||||||
|
"crossed-out",
|
||||||
|
"crossedout",
|
||||||
|
"faint",
|
||||||
|
"hidden",
|
||||||
|
"inverse",
|
||||||
|
"inverse-neg",
|
||||||
|
"inverseneg",
|
||||||
|
"italic",
|
||||||
|
"underline");
|
||||||
|
private static final String DEFAULT_LS_COLORS = "di=1;91:ex=1;92:ln=1;96:fi=";
|
||||||
|
private static final String DEFAULT_HELP_COLORS = "ti=1;34:co=1:ar=3:op=33";
|
||||||
|
private static final String DEFAULT_PRNT_COLORS = "th=1;34:rn=1;34:rs=,~grey15:mk=1;34:em=31:vs=32";
|
||||||
|
private static final String LS_COLORS = "LS_COLORS";
|
||||||
|
private static final String HELP_COLORS = "HELP_COLORS";
|
||||||
|
private static final String PRNT_COLORS = "PRNT_COLORS";
|
||||||
|
|
||||||
|
private static final String KEY = "([a-z]{2}|\\*\\.[a-zA-Z0-9]+)";
|
||||||
|
private static final String VALUE = "(([!~#]?[a-zA-Z0-9]+[a-z0-9-;]*)?|" + REGEX_TOKEN_NAME + ")";
|
||||||
|
private static final String VALUES = VALUE + "(," + VALUE + ")*";
|
||||||
|
private static final Pattern STYLE_ELEMENT_PATTERN = Pattern.compile(KEY + "=" + VALUES);
|
||||||
|
private static final Pattern STYLE_ELEMENT_SEPARATOR = Pattern.compile(":");
|
||||||
|
|
||||||
|
public static StyleResolver lsStyle() {
|
||||||
|
return style(LS_COLORS, DEFAULT_LS_COLORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StyleResolver helpStyle() {
|
||||||
|
return style(HELP_COLORS, DEFAULT_HELP_COLORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StyleResolver prntStyle() {
|
||||||
|
return style(PRNT_COLORS, DEFAULT_PRNT_COLORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isStylePattern(String style) {
|
||||||
|
final String[] styleElements = STYLE_ELEMENT_SEPARATOR.split(style);
|
||||||
|
return Arrays.stream(styleElements)
|
||||||
|
.allMatch(element -> element.isEmpty()
|
||||||
|
|| STYLE_ELEMENT_PATTERN.matcher(element).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StyleResolver style(String name, String defStyle) {
|
||||||
|
String style = consoleOption(name);
|
||||||
|
if (style == null) {
|
||||||
|
style = defStyle;
|
||||||
|
}
|
||||||
|
return style(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConsoleOptionGetter optionGetter() {
|
||||||
|
try {
|
||||||
|
return (ConsoleOptionGetter) Class.forName("org.jline.console.SystemRegistry")
|
||||||
|
.getDeclaredMethod("get")
|
||||||
|
.invoke(null);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T consoleOption(String name, T defVal) {
|
||||||
|
T out = defVal;
|
||||||
|
ConsoleOptionGetter cog = optionGetter();
|
||||||
|
if (cog != null) {
|
||||||
|
out = cog.consoleOption(name, defVal);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String consoleOption(String name) {
|
||||||
|
String out = null;
|
||||||
|
ConsoleOptionGetter cog = optionGetter();
|
||||||
|
if (cog != null) {
|
||||||
|
out = (String) cog.consoleOption(name);
|
||||||
|
if (out != null && !isStylePattern(out)) {
|
||||||
|
out = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (out == null) {
|
||||||
|
out = System.getenv(name);
|
||||||
|
if (out != null && !isStylePattern(out)) {
|
||||||
|
out = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StyleResolver style(String style) {
|
||||||
|
Map<String, String> colors = Arrays.stream(style.split(":"))
|
||||||
|
.collect(Collectors.toMap(s -> s.substring(0, s.indexOf('=')), s -> s.substring(s.indexOf('=') + 1)));
|
||||||
|
return new StyleResolver(new StyleCompiler(colors)::getStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StyleCompiler {
|
||||||
|
private static final String ANSI_VALUE = "[0-9]*(;[0-9]+){0,2}";
|
||||||
|
private static final String COLORS_24BIT = "[#x][0-9a-fA-F]{6}";
|
||||||
|
private static final List<String> COLORS_8 =
|
||||||
|
Arrays.asList("white", "black", "red", "blue", "green", "yellow", "magenta", "cyan");
|
||||||
|
// https://github.com/lhmouse/nano-win/commit/a7aab18dfeef8a0e8073d5fa420677dc8fe548da
|
||||||
|
private static final Map<String, Integer> COLORS_NANO = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
COLORS_NANO.put("pink", 204);
|
||||||
|
COLORS_NANO.put("purple", 163);
|
||||||
|
COLORS_NANO.put("mauve", 134);
|
||||||
|
COLORS_NANO.put("lagoon", 38);
|
||||||
|
COLORS_NANO.put("mint", 48);
|
||||||
|
COLORS_NANO.put("lime", 148);
|
||||||
|
COLORS_NANO.put("peach", 215);
|
||||||
|
COLORS_NANO.put("orange", 208);
|
||||||
|
COLORS_NANO.put("latte", 137);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, String> colors;
|
||||||
|
private final Map<String, String> tokenColors;
|
||||||
|
private final boolean nanoStyle;
|
||||||
|
|
||||||
|
public StyleCompiler(Map<String, String> colors) {
|
||||||
|
this(colors, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StyleCompiler(Map<String, String> colors, boolean nanoStyle) {
|
||||||
|
this.colors = colors;
|
||||||
|
this.nanoStyle = nanoStyle;
|
||||||
|
this.tokenColors = consoleOption(NANORC_THEME, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStyle(String reference) {
|
||||||
|
String rawStyle = colors.get(reference);
|
||||||
|
if (rawStyle == null) {
|
||||||
|
return null;
|
||||||
|
} else if (rawStyle.matches(REGEX_TOKEN_NAME)) {
|
||||||
|
rawStyle = tokenColors.getOrDefault(rawStyle, "normal");
|
||||||
|
} else if (!nanoStyle && rawStyle.matches(ANSI_VALUE)) {
|
||||||
|
return rawStyle;
|
||||||
|
}
|
||||||
|
StringBuilder out = new StringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
boolean fg = true;
|
||||||
|
for (String s : rawStyle.split(",")) {
|
||||||
|
if (s.trim().isEmpty()) {
|
||||||
|
fg = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!first) {
|
||||||
|
out.append(",");
|
||||||
|
}
|
||||||
|
if (ANSI_STYLES.contains(s)) {
|
||||||
|
out.append(s);
|
||||||
|
} else if (COLORS_8.contains(s)
|
||||||
|
|| COLORS_NANO.containsKey(s)
|
||||||
|
|| s.startsWith("light")
|
||||||
|
|| s.startsWith("bright")
|
||||||
|
|| s.startsWith("~")
|
||||||
|
|| s.startsWith("!")
|
||||||
|
|| s.matches("\\d+")
|
||||||
|
|| s.matches(COLORS_24BIT)
|
||||||
|
|| s.equals("normal")
|
||||||
|
|| s.equals("default")) {
|
||||||
|
if (s.matches(COLORS_24BIT)) {
|
||||||
|
if (fg) {
|
||||||
|
out.append("fg-rgb:");
|
||||||
|
} else {
|
||||||
|
out.append("bg-rgb:");
|
||||||
|
}
|
||||||
|
out.append(s);
|
||||||
|
} else if (s.matches("\\d+") || COLORS_NANO.containsKey(s)) {
|
||||||
|
if (fg) {
|
||||||
|
out.append("38;5;");
|
||||||
|
} else {
|
||||||
|
out.append("48;5;");
|
||||||
|
}
|
||||||
|
out.append(s.matches("\\d+") ? s : COLORS_NANO.get(s).toString());
|
||||||
|
} else {
|
||||||
|
if (fg) {
|
||||||
|
out.append("fg:");
|
||||||
|
} else {
|
||||||
|
out.append("bg:");
|
||||||
|
}
|
||||||
|
if (COLORS_8.contains(s) || s.startsWith("~") || s.startsWith("!") || s.startsWith("bright-")) {
|
||||||
|
out.append(s);
|
||||||
|
} else if (s.startsWith("light")) {
|
||||||
|
out.append("!").append(s.substring(5));
|
||||||
|
} else if (s.startsWith("bright")) {
|
||||||
|
out.append("!").append(s.substring(6));
|
||||||
|
} else {
|
||||||
|
out.append("default");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fg = false;
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
995
net-cli/src/main/java/org/jline/builtins/SyntaxHighlighter.java
Normal file
995
net-cli/src/main/java/org/jline/builtins/SyntaxHighlighter.java
Normal file
|
@ -0,0 +1,995 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2023, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.builtins;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
import org.jline.utils.AttributedStringBuilder;
|
||||||
|
import org.jline.utils.AttributedStyle;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
import org.jline.utils.StyleResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java implementation of nanorc highlighter
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public class SyntaxHighlighter {
|
||||||
|
public static final String REGEX_TOKEN_NAME = "[A-Z_]+";
|
||||||
|
public static final String TYPE_NANORCTHEME = ".nanorctheme";
|
||||||
|
public static final String DEFAULT_NANORC_FILE = "jnanorc";
|
||||||
|
protected static final String DEFAULT_LESSRC_FILE = "jlessrc";
|
||||||
|
protected static final String COMMAND_INCLUDE = "include";
|
||||||
|
protected static final String COMMAND_THEME = "theme";
|
||||||
|
private static final String TOKEN_NANORC = "NANORC";
|
||||||
|
private final Path nanorc;
|
||||||
|
private final String syntaxName;
|
||||||
|
private final String nanorcUrl;
|
||||||
|
private final Map<String, List<HighlightRule>> rules = new HashMap<>();
|
||||||
|
private Path currentTheme;
|
||||||
|
private boolean startEndHighlight;
|
||||||
|
private int ruleStartId = 0;
|
||||||
|
|
||||||
|
private Parser parser;
|
||||||
|
|
||||||
|
private SyntaxHighlighter() {
|
||||||
|
this(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SyntaxHighlighter(String nanorcUrl) {
|
||||||
|
this(null, null, nanorcUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SyntaxHighlighter(Path nanorc, String syntaxName) {
|
||||||
|
this(nanorc, syntaxName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SyntaxHighlighter(Path nanorc, String syntaxName, String nanorcUrl) {
|
||||||
|
this.nanorc = nanorc;
|
||||||
|
this.syntaxName = syntaxName;
|
||||||
|
this.nanorcUrl = nanorcUrl;
|
||||||
|
Map<String, List<HighlightRule>> defaultRules = new HashMap<>();
|
||||||
|
defaultRules.put(TOKEN_NANORC, new ArrayList<>());
|
||||||
|
rules.putAll(defaultRules);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static SyntaxHighlighter build(List<Path> syntaxFiles, String file, String syntaxName) {
|
||||||
|
return build(syntaxFiles, file, syntaxName, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static SyntaxHighlighter build(
|
||||||
|
List<Path> syntaxFiles, String file, String syntaxName, boolean ignoreErrors) {
|
||||||
|
SyntaxHighlighter out = new SyntaxHighlighter();
|
||||||
|
Map<String, String> colorTheme = new HashMap<>();
|
||||||
|
try {
|
||||||
|
if (syntaxName == null || !syntaxName.equals("none")) {
|
||||||
|
for (Path p : syntaxFiles) {
|
||||||
|
try {
|
||||||
|
if (colorTheme.isEmpty() && p.getFileName().toString().endsWith(TYPE_NANORCTHEME)) {
|
||||||
|
out.setCurrentTheme(p);
|
||||||
|
try (BufferedReader reader = new BufferedReader(new FileReader(p.toFile()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (line.length() > 0 && !line.startsWith("#")) {
|
||||||
|
List<String> parts = Arrays.asList(line.split("\\s+", 2));
|
||||||
|
colorTheme.put(parts.get(0), parts.get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NanorcParser nanorcParser = new NanorcParser(p, syntaxName, file, colorTheme);
|
||||||
|
nanorcParser.parse();
|
||||||
|
if (nanorcParser.matches()) {
|
||||||
|
out.addRules(nanorcParser.getHighlightRules());
|
||||||
|
out.setParser(nanorcParser.getParser());
|
||||||
|
return out;
|
||||||
|
} else if (nanorcParser.isDefault()) {
|
||||||
|
out.addRules(nanorcParser.getHighlightRules());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
if (!ignoreErrors) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build SyntaxHighlighter
|
||||||
|
*
|
||||||
|
* @param nanorc Path of nano config file jnanorc
|
||||||
|
* @param syntaxName syntax name e.g 'Java'
|
||||||
|
* @return SyntaxHighlighter
|
||||||
|
*/
|
||||||
|
public static SyntaxHighlighter build(Path nanorc, String syntaxName) {
|
||||||
|
SyntaxHighlighter out = new SyntaxHighlighter(nanorc, syntaxName);
|
||||||
|
List<Path> syntaxFiles = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
try (BufferedReader reader = new BufferedReader(new FileReader(nanorc.toFile()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (line.length() > 0 && !line.startsWith("#")) {
|
||||||
|
List<String> parts = RuleSplitter.split(line);
|
||||||
|
if (parts.get(0).equals(COMMAND_INCLUDE)) {
|
||||||
|
nanorcInclude(parts.get(1), syntaxFiles);
|
||||||
|
} else if (parts.get(0).equals(COMMAND_THEME)) {
|
||||||
|
nanorcTheme(parts.get(1), syntaxFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyntaxHighlighter sh = build(syntaxFiles, null, syntaxName);
|
||||||
|
out.addRules(sh.rules);
|
||||||
|
out.setParser(sh.parser);
|
||||||
|
out.setCurrentTheme(sh.currentTheme);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void nanorcInclude(String parameter, List<Path> syntaxFiles) throws IOException {
|
||||||
|
addFiles(parameter, s -> s.forEach(syntaxFiles::add));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void nanorcTheme(String parameter, List<Path> syntaxFiles) throws IOException {
|
||||||
|
addFiles(parameter, s -> s.findFirst().ifPresent(p -> syntaxFiles.add(0, p)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void addFiles(String parameter, Consumer<Stream<Path>> consumer) throws IOException {
|
||||||
|
if (parameter.contains("*") || parameter.contains("?")) {
|
||||||
|
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + parameter);
|
||||||
|
try (Stream<Path> pathStream = Files.walk(Paths.get(new File(parameter).getParent()))) {
|
||||||
|
consumer.accept(pathStream.filter(pathMatcher::matches));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
consumer.accept(Stream.of(Paths.get(parameter)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build SyntaxHighlighter
|
||||||
|
*
|
||||||
|
* @param nanorcUrl Url of nanorc file
|
||||||
|
* @return SyntaxHighlighter
|
||||||
|
*/
|
||||||
|
public static SyntaxHighlighter build(String nanorcUrl) {
|
||||||
|
SyntaxHighlighter out = new SyntaxHighlighter(nanorcUrl);
|
||||||
|
InputStream inputStream;
|
||||||
|
try {
|
||||||
|
if (nanorcUrl.startsWith("classpath:")) {
|
||||||
|
inputStream = new Source.ResourceSource(nanorcUrl.substring(10), null).read();
|
||||||
|
} else {
|
||||||
|
inputStream = new Source.URLSource(new URI(nanorcUrl).toURL(), null).read();
|
||||||
|
}
|
||||||
|
NanorcParser parser = new NanorcParser(inputStream, null, null);
|
||||||
|
parser.parse();
|
||||||
|
out.addRules(parser.getHighlightRules());
|
||||||
|
} catch (IOException | URISyntaxException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRules(Map<String, List<HighlightRule>> rules) {
|
||||||
|
this.rules.putAll(rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getCurrentTheme() {
|
||||||
|
return currentTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentTheme(Path currentTheme) {
|
||||||
|
this.currentTheme = currentTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParser(Parser parser) {
|
||||||
|
this.parser = parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyntaxHighlighter reset() {
|
||||||
|
ruleStartId = 0;
|
||||||
|
startEndHighlight = false;
|
||||||
|
if (parser != null) {
|
||||||
|
parser.reset();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
SyntaxHighlighter sh;
|
||||||
|
if (nanorc != null && syntaxName != null) {
|
||||||
|
sh = SyntaxHighlighter.build(nanorc, syntaxName);
|
||||||
|
} else if (nanorcUrl != null) {
|
||||||
|
sh = SyntaxHighlighter.build(nanorcUrl);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Not possible to refresh highlighter!");
|
||||||
|
}
|
||||||
|
rules.clear();
|
||||||
|
addRules(sh.rules);
|
||||||
|
parser = sh.parser;
|
||||||
|
currentTheme = sh.currentTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedString highlight(String string) {
|
||||||
|
return splitAndHighlight(new AttributedString(string));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedString highlight(AttributedStringBuilder asb) {
|
||||||
|
return splitAndHighlight(asb.toAttributedString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedString highlight(AttributedString attributedString) {
|
||||||
|
return splitAndHighlight(attributedString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributedString splitAndHighlight(AttributedString attributedString) {
|
||||||
|
AttributedStringBuilder asb = new AttributedStringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
for (AttributedString line : attributedString.columnSplitLength(Integer.MAX_VALUE)) {
|
||||||
|
if (!first) {
|
||||||
|
asb.append("\n");
|
||||||
|
}
|
||||||
|
List<ParsedToken> tokens = new ArrayList<>();
|
||||||
|
if (parser != null) {
|
||||||
|
parser.parse(line);
|
||||||
|
tokens = parser.getTokens();
|
||||||
|
}
|
||||||
|
if (tokens.isEmpty()) {
|
||||||
|
asb.append(_highlight(line, rules.get(TOKEN_NANORC)));
|
||||||
|
} else {
|
||||||
|
int pos = 0;
|
||||||
|
for (ParsedToken t : tokens) {
|
||||||
|
if (t.getStart() > pos) {
|
||||||
|
AttributedStringBuilder head =
|
||||||
|
_highlight(line.columnSubSequence(pos, t.getStart() + 1), rules.get(TOKEN_NANORC));
|
||||||
|
asb.append(head.columnSubSequence(0, head.length() - 1));
|
||||||
|
}
|
||||||
|
asb.append(_highlight(
|
||||||
|
line.columnSubSequence(t.getStart(), t.getEnd()),
|
||||||
|
rules.get(t.getName()),
|
||||||
|
t.getStartWith(),
|
||||||
|
line.columnSubSequence(t.getEnd(), line.length())));
|
||||||
|
pos = t.getEnd();
|
||||||
|
}
|
||||||
|
if (pos < line.length()) {
|
||||||
|
asb.append(_highlight(line.columnSubSequence(pos, line.length()), rules.get(TOKEN_NANORC)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return asb.toAttributedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributedStringBuilder _highlight(AttributedString line, List<HighlightRule> rules) {
|
||||||
|
return _highlight(line, rules, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributedStringBuilder _highlight(
|
||||||
|
AttributedString line, List<HighlightRule> rules, CharSequence startWith, CharSequence continueAs) {
|
||||||
|
AttributedStringBuilder asb = new AttributedStringBuilder();
|
||||||
|
asb.append(line);
|
||||||
|
if (rules.isEmpty()) {
|
||||||
|
return asb;
|
||||||
|
}
|
||||||
|
int startId = ruleStartId;
|
||||||
|
boolean endHighlight = startEndHighlight;
|
||||||
|
for (int i = startId; i < (endHighlight ? startId + 1 : rules.size()); i++) {
|
||||||
|
HighlightRule rule = rules.get(i);
|
||||||
|
switch (rule.getType()) {
|
||||||
|
case PATTERN:
|
||||||
|
asb.styleMatches(rule.getPattern(), rule.getStyle());
|
||||||
|
break;
|
||||||
|
case START_END:
|
||||||
|
boolean done = false;
|
||||||
|
Matcher start = rule.getStart().matcher(asb.toAttributedString());
|
||||||
|
Matcher end = rule.getEnd().matcher(asb.toAttributedString());
|
||||||
|
while (!done) {
|
||||||
|
AttributedStringBuilder a = new AttributedStringBuilder();
|
||||||
|
if (startEndHighlight && ruleStartId == i) {
|
||||||
|
if (end.find()) {
|
||||||
|
ruleStartId = 0;
|
||||||
|
startEndHighlight = false;
|
||||||
|
a.append(asb.columnSubSequence(0, end.end()), rule.getStyle());
|
||||||
|
a.append(_highlight(
|
||||||
|
asb.columnSubSequence(end.end(), asb.length())
|
||||||
|
.toAttributedString(),
|
||||||
|
rules));
|
||||||
|
} else {
|
||||||
|
a.append(asb, rule.getStyle());
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
asb = a;
|
||||||
|
} else {
|
||||||
|
if (start.find()) {
|
||||||
|
a.append(asb.columnSubSequence(0, start.start()));
|
||||||
|
if (end.find()) {
|
||||||
|
a.append(asb.columnSubSequence(start.start(), end.end()), rule.getStyle());
|
||||||
|
a.append(asb.columnSubSequence(end.end(), asb.length()));
|
||||||
|
} else {
|
||||||
|
ruleStartId = i;
|
||||||
|
startEndHighlight = true;
|
||||||
|
a.append(asb.columnSubSequence(start.start(), asb.length()), rule.getStyle());
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
asb = a;
|
||||||
|
} else {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PARSER_START_WITH:
|
||||||
|
if (startWith != null && startWith.toString().startsWith(rule.getStartWith())) {
|
||||||
|
asb.styleMatches(rule.getPattern(), rule.getStyle());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PARSER_CONTINUE_AS:
|
||||||
|
if (continueAs != null && continueAs.toString().matches(rule.getContinueAs() + ".*")) {
|
||||||
|
asb.styleMatches(rule.getPattern(), rule.getStyle());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return asb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HighlightRule {
|
||||||
|
private final RuleType type;
|
||||||
|
private final AttributedStyle style;
|
||||||
|
private Pattern pattern;
|
||||||
|
private Pattern start;
|
||||||
|
private Pattern end;
|
||||||
|
private String startWith;
|
||||||
|
private String continueAs;
|
||||||
|
public HighlightRule(AttributedStyle style, Pattern pattern) {
|
||||||
|
this.type = RuleType.PATTERN;
|
||||||
|
this.pattern = pattern;
|
||||||
|
this.style = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HighlightRule(AttributedStyle style, Pattern start, Pattern end) {
|
||||||
|
this.type = RuleType.START_END;
|
||||||
|
this.style = style;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HighlightRule(RuleType parserRuleType, AttributedStyle style, String value) {
|
||||||
|
this.type = parserRuleType;
|
||||||
|
this.style = style;
|
||||||
|
this.pattern = Pattern.compile(".*");
|
||||||
|
if (parserRuleType == RuleType.PARSER_START_WITH) {
|
||||||
|
this.startWith = value;
|
||||||
|
} else if (parserRuleType == RuleType.PARSER_CONTINUE_AS) {
|
||||||
|
this.continueAs = value;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Bad RuleType: " + parserRuleType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RuleType evalRuleType(List<String> colorCfg) {
|
||||||
|
RuleType out = null;
|
||||||
|
if (colorCfg.get(0).equals("color") || colorCfg.get(0).equals("icolor")) {
|
||||||
|
out = RuleType.PATTERN;
|
||||||
|
if (colorCfg.size() == 3) {
|
||||||
|
if (colorCfg.get(2).startsWith("startWith=")) {
|
||||||
|
out = RuleType.PARSER_START_WITH;
|
||||||
|
} else if (colorCfg.get(2).startsWith("continueAs=")) {
|
||||||
|
out = RuleType.PARSER_CONTINUE_AS;
|
||||||
|
}
|
||||||
|
} else if (colorCfg.size() == 4) {
|
||||||
|
if (colorCfg.get(2).startsWith("start=") && colorCfg.get(3).startsWith("end=")) {
|
||||||
|
out = RuleType.START_END;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RuleType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedStyle getStyle() {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pattern getPattern() {
|
||||||
|
if (type == RuleType.START_END) {
|
||||||
|
throw new IllegalAccessError();
|
||||||
|
}
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pattern getStart() {
|
||||||
|
if (type == RuleType.PATTERN) {
|
||||||
|
throw new IllegalAccessError();
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pattern getEnd() {
|
||||||
|
if (type == RuleType.PATTERN) {
|
||||||
|
throw new IllegalAccessError();
|
||||||
|
}
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStartWith() {
|
||||||
|
return startWith;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContinueAs() {
|
||||||
|
return continueAs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "{type:" + type
|
||||||
|
+ ", startWith: " + startWith
|
||||||
|
+ ", continueAs: " + continueAs
|
||||||
|
+ ", start: " + start
|
||||||
|
+ ", end: " + end
|
||||||
|
+ ", pattern: " + pattern
|
||||||
|
+ "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RuleType {
|
||||||
|
PATTERN,
|
||||||
|
START_END,
|
||||||
|
PARSER_START_WITH,
|
||||||
|
PARSER_CONTINUE_AS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NanorcParser {
|
||||||
|
private static final String DEFAULT_SYNTAX = "default";
|
||||||
|
private final String name;
|
||||||
|
private final String target;
|
||||||
|
private final Map<String, List<HighlightRule>> highlightRules = new HashMap<>();
|
||||||
|
private final BufferedReader reader;
|
||||||
|
private Map<String, String> colorTheme = new HashMap<>();
|
||||||
|
private boolean matches = false;
|
||||||
|
private String syntaxName = "unknown";
|
||||||
|
|
||||||
|
private Parser parser;
|
||||||
|
|
||||||
|
public NanorcParser(Path file, String name, String target, Map<String, String> colorTheme) throws IOException {
|
||||||
|
this(new Source.PathSource(file, null).read(), name, target);
|
||||||
|
this.colorTheme = colorTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NanorcParser(InputStream in, String name, String target) {
|
||||||
|
this.reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||||
|
this.name = name;
|
||||||
|
this.target = target;
|
||||||
|
highlightRules.put(TOKEN_NANORC, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parse() throws IOException {
|
||||||
|
String line;
|
||||||
|
int idx = 0;
|
||||||
|
try {
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
idx++;
|
||||||
|
line = line.trim();
|
||||||
|
if (line.length() > 0 && !line.startsWith("#")) {
|
||||||
|
List<String> parts = RuleSplitter.split(fixRegexes(line));
|
||||||
|
if (parts.get(0).equals("syntax")) {
|
||||||
|
syntaxName = parts.get(1);
|
||||||
|
List<Pattern> filePatterns = new ArrayList<>();
|
||||||
|
if (name != null) {
|
||||||
|
if (name.equals(syntaxName)) {
|
||||||
|
matches = true;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (target != null) {
|
||||||
|
for (int i = 2; i < parts.size(); i++) {
|
||||||
|
filePatterns.add(Pattern.compile(parts.get(i)));
|
||||||
|
}
|
||||||
|
for (Pattern p : filePatterns) {
|
||||||
|
if (p.matcher(target).find()) {
|
||||||
|
matches = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matches && !syntaxName.equals(DEFAULT_SYNTAX)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
matches = true;
|
||||||
|
}
|
||||||
|
} else if (parts.get(0).startsWith("$")) {
|
||||||
|
String key = themeKey(parts.get(0));
|
||||||
|
if (colorTheme.containsKey(key)) {
|
||||||
|
if (parser == null) {
|
||||||
|
parser = new Parser();
|
||||||
|
}
|
||||||
|
String[] args = parts.get(1).split(",\\s*");
|
||||||
|
boolean validKey = true;
|
||||||
|
if (key.startsWith("$BLOCK_COMMENT")) {
|
||||||
|
parser.setBlockCommentDelimiters(key, args);
|
||||||
|
} else if (key.startsWith("$LINE_COMMENT")) {
|
||||||
|
parser.setLineCommentDelimiters(key, args);
|
||||||
|
} else if (key.startsWith("$BALANCED_DELIMITERS")) {
|
||||||
|
parser.setBalancedDelimiters(key, args);
|
||||||
|
} else {
|
||||||
|
Log.warn("Unknown token type: ", key);
|
||||||
|
validKey = false;
|
||||||
|
}
|
||||||
|
if (validKey) {
|
||||||
|
if (!highlightRules.containsKey(key)) {
|
||||||
|
highlightRules.put(key, new ArrayList<>());
|
||||||
|
}
|
||||||
|
for (String l : colorTheme.get(key).split("\\\\n")) {
|
||||||
|
idx++;
|
||||||
|
addHighlightRule(RuleSplitter.split(fixRegexes(l)), idx, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.warn("Unknown token type: ", key);
|
||||||
|
}
|
||||||
|
} else if (!addHighlightRule(parts, idx, TOKEN_NANORC)
|
||||||
|
&& parts.get(0).matches("\\+" + REGEX_TOKEN_NAME)) {
|
||||||
|
String key = themeKey(parts.get(0));
|
||||||
|
String theme = colorTheme.get(key);
|
||||||
|
if (theme != null) {
|
||||||
|
for (String l : theme.split("\\\\n")) {
|
||||||
|
idx++;
|
||||||
|
addHighlightRule(RuleSplitter.split(fixRegexes(l)), idx, TOKEN_NANORC);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.warn("Unknown token type: ", key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String fixRegexes(String line) {
|
||||||
|
return line.replaceAll("\\\\<", "\\\\b")
|
||||||
|
.replaceAll("\\\\>", "\\\\b")
|
||||||
|
.replaceAll("\\[:alnum:]", "\\\\p{Alnum}")
|
||||||
|
.replaceAll("\\[:alpha:]", "\\\\p{Alpha}")
|
||||||
|
.replaceAll("\\[:blank:]", "\\\\p{Blank}")
|
||||||
|
.replaceAll("\\[:cntrl:]", "\\\\p{Cntrl}")
|
||||||
|
.replaceAll("\\[:digit:]", "\\\\p{Digit}")
|
||||||
|
.replaceAll("\\[:graph:]", "\\\\p{Graph}")
|
||||||
|
.replaceAll("\\[:lower:]", "\\\\p{Lower}")
|
||||||
|
.replaceAll("\\[:print:]", "\\\\p{Print}")
|
||||||
|
.replaceAll("\\[:punct:]", "\\\\p{Punct}")
|
||||||
|
.replaceAll("\\[:space:]", "\\\\s")
|
||||||
|
.replaceAll("\\[:upper:]", "\\\\p{Upper}")
|
||||||
|
.replaceAll("\\[:xdigit:]", "\\\\p{XDigit}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean addHighlightRule(List<String> parts, int idx, String tokenName) {
|
||||||
|
boolean out = true;
|
||||||
|
if (parts.get(0).equals("color")) {
|
||||||
|
addHighlightRule(syntaxName + idx, parts, false, tokenName);
|
||||||
|
} else if (parts.get(0).equals("icolor")) {
|
||||||
|
addHighlightRule(syntaxName + idx, parts, true, tokenName);
|
||||||
|
} else if (parts.get(0).matches(REGEX_TOKEN_NAME + "[:]?")) {
|
||||||
|
String key = themeKey(parts.get(0));
|
||||||
|
String theme = colorTheme.get(key);
|
||||||
|
if (theme != null) {
|
||||||
|
parts.set(0, "color");
|
||||||
|
parts.add(1, theme);
|
||||||
|
addHighlightRule(syntaxName + idx, parts, false, tokenName);
|
||||||
|
} else {
|
||||||
|
Log.warn("Unknown token type: ", key);
|
||||||
|
}
|
||||||
|
} else if (parts.get(0).matches("~" + REGEX_TOKEN_NAME + "[:]?")) {
|
||||||
|
String key = themeKey(parts.get(0));
|
||||||
|
String theme = colorTheme.get(key);
|
||||||
|
if (theme != null) {
|
||||||
|
parts.set(0, "icolor");
|
||||||
|
parts.add(1, theme);
|
||||||
|
addHighlightRule(syntaxName + idx, parts, true, tokenName);
|
||||||
|
} else {
|
||||||
|
Log.warn("Unknown token type: ", key);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out = false;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String themeKey(String key) {
|
||||||
|
if (key.startsWith("+")) {
|
||||||
|
return key;
|
||||||
|
} else {
|
||||||
|
int keyEnd = key.endsWith(":") ? key.length() - 1 : key.length();
|
||||||
|
if (key.startsWith("~")) {
|
||||||
|
return key.substring(1, keyEnd);
|
||||||
|
}
|
||||||
|
return key.substring(0, keyEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches() {
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parser getParser() {
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<HighlightRule>> getHighlightRules() {
|
||||||
|
return highlightRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefault() {
|
||||||
|
return syntaxName.equals(DEFAULT_SYNTAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addHighlightRule(String reference, List<String> parts, boolean caseInsensitive, String tokenName) {
|
||||||
|
Map<String, String> spec = new HashMap<>();
|
||||||
|
spec.put(reference, parts.get(1));
|
||||||
|
Styles.StyleCompiler sh = new Styles.StyleCompiler(spec, true);
|
||||||
|
AttributedStyle style = new StyleResolver(sh::getStyle).resolve("." + reference);
|
||||||
|
|
||||||
|
if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.PATTERN) {
|
||||||
|
if (parts.size() == 2) {
|
||||||
|
highlightRules.get(tokenName).add(new HighlightRule(style, doPattern(".*", caseInsensitive)));
|
||||||
|
} else {
|
||||||
|
for (int i = 2; i < parts.size(); i++) {
|
||||||
|
highlightRules
|
||||||
|
.get(tokenName)
|
||||||
|
.add(new HighlightRule(style, doPattern(parts.get(i), caseInsensitive)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.START_END) {
|
||||||
|
String s = parts.get(2);
|
||||||
|
String e = parts.get(3);
|
||||||
|
highlightRules
|
||||||
|
.get(tokenName)
|
||||||
|
.add(new HighlightRule(
|
||||||
|
style,
|
||||||
|
doPattern(s.substring(7, s.length() - 1), caseInsensitive),
|
||||||
|
doPattern(e.substring(5, e.length() - 1), caseInsensitive)));
|
||||||
|
} else if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.PARSER_START_WITH) {
|
||||||
|
highlightRules
|
||||||
|
.get(tokenName)
|
||||||
|
.add(new HighlightRule(
|
||||||
|
HighlightRule.RuleType.PARSER_START_WITH,
|
||||||
|
style,
|
||||||
|
parts.get(2).substring(10)));
|
||||||
|
} else if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.PARSER_CONTINUE_AS) {
|
||||||
|
highlightRules
|
||||||
|
.get(tokenName)
|
||||||
|
.add(new HighlightRule(
|
||||||
|
HighlightRule.RuleType.PARSER_CONTINUE_AS,
|
||||||
|
style,
|
||||||
|
parts.get(2).substring(11)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pattern doPattern(String regex, boolean caseInsensitive) {
|
||||||
|
return caseInsensitive ? Pattern.compile(regex, Pattern.CASE_INSENSITIVE) : Pattern.compile(regex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class RuleSplitter {
|
||||||
|
protected static List<String> split(String s) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
if (s.length() == 0) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
int depth = 0;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
char c = s.charAt(i);
|
||||||
|
if (c == '"') {
|
||||||
|
if (depth == 0) {
|
||||||
|
depth = 1;
|
||||||
|
} else {
|
||||||
|
char nextChar = i < s.length() - 1 ? s.charAt(i + 1) : ' ';
|
||||||
|
if (nextChar == ' ') {
|
||||||
|
depth = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (c == ' ' && depth == 0 && sb.length() > 0) {
|
||||||
|
out.add(stripQuotes(sb.toString()));
|
||||||
|
sb = new StringBuilder();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sb.length() > 0 || (c != ' ' && c != '\t')) {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
out.add(stripQuotes(sb.toString()));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String stripQuotes(String s) {
|
||||||
|
String out = s.trim();
|
||||||
|
if (s.startsWith("\"") && s.endsWith("\"")) {
|
||||||
|
out = s.substring(1, s.length() - 1);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BlockCommentDelimiters {
|
||||||
|
private final String start;
|
||||||
|
private final String end;
|
||||||
|
|
||||||
|
public BlockCommentDelimiters(String[] args) {
|
||||||
|
if (args.length != 2
|
||||||
|
|| args[0] == null
|
||||||
|
|| args[1] == null
|
||||||
|
|| args[0].isEmpty()
|
||||||
|
|| args[1].isEmpty()
|
||||||
|
|| args[0].equals(args[1])) {
|
||||||
|
throw new IllegalArgumentException("Bad block comment delimiters!");
|
||||||
|
}
|
||||||
|
start = args[0];
|
||||||
|
end = args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ParsedToken {
|
||||||
|
private final String name;
|
||||||
|
private final CharSequence startWith;
|
||||||
|
private final int start;
|
||||||
|
private final int end;
|
||||||
|
|
||||||
|
public ParsedToken(String name, CharSequence startWith, int start, int end) {
|
||||||
|
this.name = name;
|
||||||
|
this.startWith = startWith;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getStartWith() {
|
||||||
|
return startWith;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Parser {
|
||||||
|
private static final char escapeChar = '\\';
|
||||||
|
private String blockCommentTokenName;
|
||||||
|
private BlockCommentDelimiters blockCommentDelimiters;
|
||||||
|
private String lineCommentTokenName;
|
||||||
|
private String[] lineCommentDelimiters;
|
||||||
|
private String balancedDelimiterTokenName;
|
||||||
|
private String[] balancedDelimiters;
|
||||||
|
private String balancedDelimiter;
|
||||||
|
private List<ParsedToken> tokens;
|
||||||
|
private CharSequence startWith;
|
||||||
|
private int tokenStart = 0;
|
||||||
|
private boolean blockComment;
|
||||||
|
private boolean lineComment;
|
||||||
|
private boolean balancedQuoted;
|
||||||
|
|
||||||
|
public Parser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockCommentDelimiters(String tokenName, String[] args) {
|
||||||
|
try {
|
||||||
|
blockCommentTokenName = tokenName;
|
||||||
|
blockCommentDelimiters = new BlockCommentDelimiters(args);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.warn(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLineCommentDelimiters(String tokenName, String[] args) {
|
||||||
|
lineCommentTokenName = tokenName;
|
||||||
|
lineCommentDelimiters = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBalancedDelimiters(String tokenName, String[] args) {
|
||||||
|
balancedDelimiterTokenName = tokenName;
|
||||||
|
balancedDelimiters = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
startWith = null;
|
||||||
|
blockComment = false;
|
||||||
|
lineComment = false;
|
||||||
|
balancedQuoted = false;
|
||||||
|
tokenStart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parse(final CharSequence line) {
|
||||||
|
if (line == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tokens = new ArrayList<>();
|
||||||
|
if (blockComment || balancedQuoted) {
|
||||||
|
tokenStart = 0;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < line.length(); i++) {
|
||||||
|
if (isEscapeChar(line, i) || isEscaped(line, i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!blockComment && !lineComment && !balancedQuoted) {
|
||||||
|
if (blockCommentDelimiters != null && isDelimiter(line, i, blockCommentDelimiters.getStart())) {
|
||||||
|
blockComment = true;
|
||||||
|
tokenStart = i;
|
||||||
|
startWith = startWithSubstring(line, i);
|
||||||
|
i = i + blockCommentDelimiters.getStart().length() - 1;
|
||||||
|
} else if (isLineCommentDelimiter(line, i)) {
|
||||||
|
lineComment = true;
|
||||||
|
tokenStart = i;
|
||||||
|
startWith = startWithSubstring(line, i);
|
||||||
|
break;
|
||||||
|
} else if ((balancedDelimiter = balancedDelimiter(line, i)) != null) {
|
||||||
|
balancedQuoted = true;
|
||||||
|
tokenStart = i;
|
||||||
|
startWith = startWithSubstring(line, i);
|
||||||
|
i = i + balancedDelimiter.length() - 1;
|
||||||
|
}
|
||||||
|
} else if (blockComment) {
|
||||||
|
if (isDelimiter(line, i, blockCommentDelimiters.getEnd())) {
|
||||||
|
blockComment = false;
|
||||||
|
i = i + blockCommentDelimiters.getEnd().length() - 1;
|
||||||
|
tokens.add(new ParsedToken(blockCommentTokenName, startWith, tokenStart, i + 1));
|
||||||
|
}
|
||||||
|
} else if (balancedQuoted) {
|
||||||
|
if (isDelimiter(line, i, balancedDelimiter)) {
|
||||||
|
balancedQuoted = false;
|
||||||
|
i = i + balancedDelimiter.length() - 1;
|
||||||
|
if (i - tokenStart + 1 > 2 * balancedDelimiter.length()) {
|
||||||
|
tokens.add(new ParsedToken(balancedDelimiterTokenName, startWith, tokenStart, i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blockComment) {
|
||||||
|
tokens.add(new ParsedToken(blockCommentTokenName, startWith, tokenStart, line.length()));
|
||||||
|
} else if (lineComment) {
|
||||||
|
lineComment = false;
|
||||||
|
tokens.add(new ParsedToken(lineCommentTokenName, startWith, tokenStart, line.length()));
|
||||||
|
} else if (balancedQuoted) {
|
||||||
|
tokens.add(new ParsedToken(balancedDelimiterTokenName, startWith, tokenStart, line.length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence startWithSubstring(CharSequence line, int pos) {
|
||||||
|
return line.subSequence(pos, Math.min(pos + 5, line.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ParsedToken> getTokens() {
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String balancedDelimiter(final CharSequence buffer, final int pos) {
|
||||||
|
if (balancedDelimiters != null) {
|
||||||
|
for (String delimiter : balancedDelimiters) {
|
||||||
|
if (isDelimiter(buffer, pos, delimiter)) {
|
||||||
|
return delimiter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDelimiter(final CharSequence buffer, final int pos, final String delimiter) {
|
||||||
|
if (pos < 0 || delimiter == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int length = delimiter.length();
|
||||||
|
if (length <= buffer.length() - pos) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (delimiter.charAt(i) != buffer.charAt(pos + i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLineCommentDelimiter(final CharSequence buffer, final int pos) {
|
||||||
|
if (lineCommentDelimiters != null) {
|
||||||
|
for (String delimiter : lineCommentDelimiters) {
|
||||||
|
if (isDelimiter(buffer, pos, delimiter)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEscapeChar(char ch) {
|
||||||
|
return escapeChar == ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this character is a valid escape char (i.e. one that has not been escaped)
|
||||||
|
*
|
||||||
|
* @param buffer the buffer to check in
|
||||||
|
* @param pos the position of the character to check
|
||||||
|
* @return true if the character at the specified position in the given buffer is an escape
|
||||||
|
* character and the character immediately preceding it is not an escape character.
|
||||||
|
*/
|
||||||
|
private boolean isEscapeChar(final CharSequence buffer, final int pos) {
|
||||||
|
if (pos < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char ch = buffer.charAt(pos);
|
||||||
|
return isEscapeChar(ch) && !isEscaped(buffer, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a character is escaped (i.e. if the previous character is an escape)
|
||||||
|
*
|
||||||
|
* @param buffer the buffer to check in
|
||||||
|
* @param pos the position of the character to check
|
||||||
|
* @return true if the character at the specified position in the given buffer is an escape
|
||||||
|
* character and the character immediately preceding it is an escape character.
|
||||||
|
*/
|
||||||
|
private boolean isEscaped(final CharSequence buffer, final int pos) {
|
||||||
|
if (pos <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isEscapeChar(buffer, pos - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
644
net-cli/src/main/java/org/jline/builtins/TTop.java
Normal file
644
net-cli/src/main/java/org/jline/builtins/TTop.java
Normal file
|
@ -0,0 +1,644 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.builtins;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.lang.management.ClassLoadingMXBean;
|
||||||
|
import java.lang.management.GarbageCollectorMXBean;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.management.MemoryMXBean;
|
||||||
|
import java.lang.management.OperatingSystemMXBean;
|
||||||
|
import java.lang.management.ThreadInfo;
|
||||||
|
import java.lang.management.ThreadMXBean;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.DecimalFormatSymbols;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.jline.builtins.Options.HelpException;
|
||||||
|
import org.jline.keymap.BindingReader;
|
||||||
|
import org.jline.keymap.KeyMap;
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Size;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
import org.jline.utils.AttributedStringBuilder;
|
||||||
|
import org.jline.utils.Display;
|
||||||
|
import org.jline.utils.InfoCmp;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
import org.jline.utils.NonBlockingReader;
|
||||||
|
import static org.jline.builtins.TTop.Align.Left;
|
||||||
|
import static org.jline.builtins.TTop.Align.Right;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread Top implementation.
|
||||||
|
* <p>
|
||||||
|
* TODO: option modification at runtime (such as implemented in less) is not currently supported
|
||||||
|
* TODO: one possible addition would be to detect deadlock threads and display them in a specific way
|
||||||
|
*/
|
||||||
|
public class TTop {
|
||||||
|
|
||||||
|
public static final String STAT_UPTIME = "uptime";
|
||||||
|
|
||||||
|
public static final String STAT_TID = "tid";
|
||||||
|
public static final String STAT_NAME = "name";
|
||||||
|
public static final String STAT_STATE = "state";
|
||||||
|
public static final String STAT_BLOCKED_TIME = "blocked_time";
|
||||||
|
public static final String STAT_BLOCKED_COUNT = "blocked_count";
|
||||||
|
public static final String STAT_WAITED_TIME = "waited_time";
|
||||||
|
public static final String STAT_WAITED_COUNT = "waited_count";
|
||||||
|
public static final String STAT_LOCK_NAME = "lock_name";
|
||||||
|
public static final String STAT_LOCK_OWNER_ID = "lock_owner_id";
|
||||||
|
public static final String STAT_LOCK_OWNER_NAME = "lock_owner_name";
|
||||||
|
public static final String STAT_USER_TIME = "user_time";
|
||||||
|
public static final String STAT_USER_TIME_PERC = "user_time_perc";
|
||||||
|
public static final String STAT_CPU_TIME = "cpu_time";
|
||||||
|
public static final String STAT_CPU_TIME_PERC = "cpu_time_perc";
|
||||||
|
private final Map<String, Column> columns = new LinkedHashMap<>();
|
||||||
|
private final Terminal terminal;
|
||||||
|
private final Display display;
|
||||||
|
private final BindingReader bindingReader;
|
||||||
|
private final KeyMap<Operation> keys;
|
||||||
|
|
||||||
|
private final Size size = new Size();
|
||||||
|
public List<String> sort;
|
||||||
|
public long delay;
|
||||||
|
public List<String> stats;
|
||||||
|
public int nthreads;
|
||||||
|
private Comparator<Map<String, Comparable<?>>> comparator;
|
||||||
|
// Internal cache data
|
||||||
|
private final Map<Long, Map<String, Object>> previous = new HashMap<>();
|
||||||
|
private final Map<Long, Map<String, Long>> changes = new HashMap<>();
|
||||||
|
private final Map<String, Integer> widths = new HashMap<>();
|
||||||
|
|
||||||
|
public TTop(Terminal terminal) {
|
||||||
|
this.terminal = terminal;
|
||||||
|
this.display = new Display(terminal, true);
|
||||||
|
this.bindingReader = new BindingReader(terminal.reader());
|
||||||
|
|
||||||
|
DecimalFormatSymbols dfs = new DecimalFormatSymbols();
|
||||||
|
dfs.setDecimalSeparator('.');
|
||||||
|
DecimalFormat perc = new DecimalFormat("0.00%", dfs);
|
||||||
|
|
||||||
|
register(STAT_TID, Right, "TID", o -> String.format("%3d", (Long) o));
|
||||||
|
register(STAT_NAME, Left, "NAME", padcut(40));
|
||||||
|
register(STAT_STATE, Left, "STATE", o -> o.toString().toLowerCase());
|
||||||
|
register(STAT_BLOCKED_TIME, Right, "T-BLOCKED", o -> millis((Long) o));
|
||||||
|
register(STAT_BLOCKED_COUNT, Right, "#-BLOCKED", Object::toString);
|
||||||
|
register(STAT_WAITED_TIME, Right, "T-WAITED", o -> millis((Long) o));
|
||||||
|
register(STAT_WAITED_COUNT, Right, "#-WAITED", Object::toString);
|
||||||
|
register(STAT_LOCK_NAME, Left, "LOCK-NAME", Object::toString);
|
||||||
|
register(STAT_LOCK_OWNER_ID, Right, "LOCK-OWNER-ID", id -> ((Long) id) >= 0 ? id.toString() : "");
|
||||||
|
register(STAT_LOCK_OWNER_NAME, Left, "LOCK-OWNER-NAME", name -> name != null ? name.toString() : "");
|
||||||
|
register(STAT_USER_TIME, Right, "T-USR", o -> nanos((Long) o));
|
||||||
|
register(STAT_CPU_TIME, Right, "T-CPU", o -> nanos((Long) o));
|
||||||
|
register(STAT_USER_TIME_PERC, Right, "%-USR", perc::format);
|
||||||
|
register(STAT_CPU_TIME_PERC, Right, "%-CPU", perc::format);
|
||||||
|
|
||||||
|
keys = new KeyMap<>();
|
||||||
|
bindKeys(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ttop(Terminal terminal, PrintStream out, PrintStream err, String[] argv) throws Exception {
|
||||||
|
final String[] usage = {
|
||||||
|
"ttop - display and update sorted information about threads",
|
||||||
|
"Usage: ttop [OPTIONS]",
|
||||||
|
" -? --help Show help",
|
||||||
|
" -o --order=ORDER Comma separated list of sorting keys",
|
||||||
|
" -t --stats=STATS Comma separated list of stats to display",
|
||||||
|
" -s --seconds=SECONDS Delay between updates in seconds",
|
||||||
|
" -m --millis=MILLIS Delay between updates in milliseconds",
|
||||||
|
" -n --nthreads=NTHREADS Only display up to NTHREADS threads",
|
||||||
|
};
|
||||||
|
Options opt = Options.compile(usage).parse(argv);
|
||||||
|
if (opt.isSet("help")) {
|
||||||
|
throw new HelpException(opt.usage());
|
||||||
|
}
|
||||||
|
TTop ttop = new TTop(terminal);
|
||||||
|
ttop.sort = opt.isSet("order") ? Arrays.asList(opt.get("order").split(",")) : null;
|
||||||
|
ttop.delay = opt.isSet("seconds") ? opt.getNumber("seconds") * 1000L : ttop.delay;
|
||||||
|
ttop.delay = opt.isSet("millis") ? opt.getNumber("millis") : ttop.delay;
|
||||||
|
ttop.stats = opt.isSet("stats") ? Arrays.asList(opt.get("stats").split(",")) : null;
|
||||||
|
ttop.nthreads = opt.isSet("nthreads") ? opt.getNumber("nthreads") : ttop.nthreads;
|
||||||
|
ttop.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String nanos(long nanos) {
|
||||||
|
return millis(nanos / 1_000_000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String millis(long millis) {
|
||||||
|
long secs = millis / 1_000;
|
||||||
|
millis = millis % 1000;
|
||||||
|
long mins = secs / 60;
|
||||||
|
secs = secs % 60;
|
||||||
|
long hours = mins / 60;
|
||||||
|
mins = mins % 60;
|
||||||
|
if (hours > 0) {
|
||||||
|
return String.format("%d:%02d:%02d.%03d", hours, mins, secs, millis);
|
||||||
|
} else if (mins > 0) {
|
||||||
|
return String.format("%d:%02d.%03d", mins, secs, millis);
|
||||||
|
} else {
|
||||||
|
return String.format("%d.%03d", secs, millis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Function<Object, String> padcut(int nb) {
|
||||||
|
return o -> padcut(o.toString(), nb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String padcut(String str, int nb) {
|
||||||
|
if (str.length() <= nb) {
|
||||||
|
StringBuilder sb = new StringBuilder(nb);
|
||||||
|
sb.append(str);
|
||||||
|
while (sb.length() < nb) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} else {
|
||||||
|
return str.substring(0, nb - 3) + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String memory(long cur, long max) {
|
||||||
|
if (max > 0) {
|
||||||
|
String smax = humanReadableByteCount(max, false);
|
||||||
|
String cmax = humanReadableByteCount(cur, false);
|
||||||
|
StringBuilder sb = new StringBuilder(smax.length() * 2 + 3);
|
||||||
|
for (int i = cmax.length(); i < smax.length(); i++) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(cmax).append(" / ").append(smax);
|
||||||
|
return sb.toString();
|
||||||
|
} else {
|
||||||
|
return humanReadableByteCount(cur, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String humanReadableByteCount(long bytes, boolean si) {
|
||||||
|
int unit = si ? 1000 : 1024;
|
||||||
|
if (bytes < 1024) return bytes + " B";
|
||||||
|
int exp = (int) (Math.log(bytes) / Math.log(1024));
|
||||||
|
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
|
||||||
|
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyMap<Operation> getKeys() {
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() throws IOException, InterruptedException {
|
||||||
|
comparator = buildComparator(sort);
|
||||||
|
delay = delay > 0 ? Math.max(delay, 100) : 1000;
|
||||||
|
if (stats == null || stats.isEmpty()) {
|
||||||
|
stats = new ArrayList<>(Arrays.asList(STAT_TID, STAT_NAME, STAT_STATE, STAT_CPU_TIME, STAT_LOCK_OWNER_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean isThreadContentionMonitoringEnabled = null;
|
||||||
|
ThreadMXBean threadsBean = ManagementFactory.getThreadMXBean();
|
||||||
|
if (stats.contains(STAT_BLOCKED_TIME)
|
||||||
|
|| stats.contains(STAT_BLOCKED_COUNT)
|
||||||
|
|| stats.contains(STAT_WAITED_TIME)
|
||||||
|
|| stats.contains(STAT_WAITED_COUNT)) {
|
||||||
|
if (threadsBean.isThreadContentionMonitoringSupported()) {
|
||||||
|
isThreadContentionMonitoringEnabled = threadsBean.isThreadContentionMonitoringEnabled();
|
||||||
|
if (!isThreadContentionMonitoringEnabled) {
|
||||||
|
threadsBean.setThreadContentionMonitoringEnabled(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stats.removeAll(
|
||||||
|
Arrays.asList(STAT_BLOCKED_TIME, STAT_BLOCKED_COUNT, STAT_WAITED_TIME, STAT_WAITED_COUNT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Boolean isThreadCpuTimeEnabled = null;
|
||||||
|
if (stats.contains(STAT_USER_TIME) || stats.contains(STAT_CPU_TIME)) {
|
||||||
|
if (threadsBean.isThreadCpuTimeSupported()) {
|
||||||
|
isThreadCpuTimeEnabled = threadsBean.isThreadCpuTimeEnabled();
|
||||||
|
if (!isThreadCpuTimeEnabled) {
|
||||||
|
threadsBean.setThreadCpuTimeEnabled(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stats.removeAll(Arrays.asList(STAT_USER_TIME, STAT_CPU_TIME));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size.copy(terminal.getSize());
|
||||||
|
Terminal.SignalHandler prevHandler = terminal.handle(Terminal.Signal.WINCH, this::handle);
|
||||||
|
Attributes attr = terminal.enterRawMode();
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Use alternate buffer
|
||||||
|
if (!terminal.puts(InfoCmp.Capability.enter_ca_mode)) {
|
||||||
|
terminal.puts(InfoCmp.Capability.clear_screen);
|
||||||
|
}
|
||||||
|
terminal.puts(InfoCmp.Capability.keypad_xmit);
|
||||||
|
terminal.puts(InfoCmp.Capability.cursor_invisible);
|
||||||
|
terminal.writer().flush();
|
||||||
|
|
||||||
|
long t0 = System.currentTimeMillis();
|
||||||
|
|
||||||
|
Operation op;
|
||||||
|
do {
|
||||||
|
display();
|
||||||
|
checkInterrupted();
|
||||||
|
|
||||||
|
op = null;
|
||||||
|
|
||||||
|
long delta = ((System.currentTimeMillis() - t0) / delay + 1) * delay + t0 - System.currentTimeMillis();
|
||||||
|
int ch = bindingReader.peekCharacter(delta);
|
||||||
|
if (ch == -1) {
|
||||||
|
op = Operation.EXIT;
|
||||||
|
} else if (ch != NonBlockingReader.READ_EXPIRED) {
|
||||||
|
op = bindingReader.readBinding(keys, null, false);
|
||||||
|
}
|
||||||
|
if (op == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
case INCREASE_DELAY:
|
||||||
|
delay = delay * 2;
|
||||||
|
t0 = System.currentTimeMillis();
|
||||||
|
break;
|
||||||
|
case DECREASE_DELAY:
|
||||||
|
delay = Math.max(delay / 2, 16);
|
||||||
|
t0 = System.currentTimeMillis();
|
||||||
|
break;
|
||||||
|
case CLEAR:
|
||||||
|
display.clear();
|
||||||
|
break;
|
||||||
|
case REVERSE:
|
||||||
|
comparator = comparator.reversed();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (op != Operation.EXIT);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
// Do nothing
|
||||||
|
} catch (Error err) {
|
||||||
|
Log.info("Error: ", err);
|
||||||
|
} finally {
|
||||||
|
terminal.setAttributes(attr);
|
||||||
|
if (prevHandler != null) {
|
||||||
|
terminal.handle(Terminal.Signal.WINCH, prevHandler);
|
||||||
|
}
|
||||||
|
// Use main buffer
|
||||||
|
if (!terminal.puts(InfoCmp.Capability.exit_ca_mode)) {
|
||||||
|
terminal.puts(InfoCmp.Capability.clear_screen);
|
||||||
|
}
|
||||||
|
terminal.puts(InfoCmp.Capability.keypad_local);
|
||||||
|
terminal.puts(InfoCmp.Capability.cursor_visible);
|
||||||
|
terminal.writer().flush();
|
||||||
|
|
||||||
|
if (isThreadContentionMonitoringEnabled != null) {
|
||||||
|
threadsBean.setThreadContentionMonitoringEnabled(isThreadContentionMonitoringEnabled);
|
||||||
|
}
|
||||||
|
if (isThreadCpuTimeEnabled != null) {
|
||||||
|
threadsBean.setThreadCpuTimeEnabled(isThreadCpuTimeEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handle(Terminal.Signal signal) {
|
||||||
|
int prevw = size.getColumns();
|
||||||
|
size.copy(terminal.getSize());
|
||||||
|
try {
|
||||||
|
if (size.getColumns() < prevw) {
|
||||||
|
display.clear();
|
||||||
|
}
|
||||||
|
display();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Comparable<?>>> infos() {
|
||||||
|
long ctime = ManagementFactory.getRuntimeMXBean().getUptime();
|
||||||
|
Long ptime = (Long) previous.computeIfAbsent(-1L, id -> new HashMap<>()).put(STAT_UPTIME, ctime);
|
||||||
|
long delta = ptime != null ? ctime - ptime : 0L;
|
||||||
|
|
||||||
|
ThreadMXBean threadsBean = ManagementFactory.getThreadMXBean();
|
||||||
|
ThreadInfo[] infos = threadsBean.dumpAllThreads(false, false);
|
||||||
|
List<Map<String, Comparable<?>>> threads = new ArrayList<>();
|
||||||
|
for (ThreadInfo ti : infos) {
|
||||||
|
Map<String, Comparable<?>> t = new HashMap<>();
|
||||||
|
t.put(STAT_TID, ti.getThreadId());
|
||||||
|
t.put(STAT_NAME, ti.getThreadName());
|
||||||
|
t.put(STAT_STATE, ti.getThreadState());
|
||||||
|
if (threadsBean.isThreadContentionMonitoringEnabled()) {
|
||||||
|
t.put(STAT_BLOCKED_TIME, ti.getBlockedTime());
|
||||||
|
t.put(STAT_BLOCKED_COUNT, ti.getBlockedCount());
|
||||||
|
t.put(STAT_WAITED_TIME, ti.getWaitedTime());
|
||||||
|
t.put(STAT_WAITED_COUNT, ti.getWaitedCount());
|
||||||
|
}
|
||||||
|
t.put(STAT_LOCK_NAME, ti.getLockName());
|
||||||
|
t.put(STAT_LOCK_OWNER_ID, ti.getLockOwnerId());
|
||||||
|
t.put(STAT_LOCK_OWNER_NAME, ti.getLockOwnerName());
|
||||||
|
if (threadsBean.isThreadCpuTimeSupported() && threadsBean.isThreadCpuTimeEnabled()) {
|
||||||
|
long tid = ti.getThreadId(), t0, t1;
|
||||||
|
// Cpu
|
||||||
|
t1 = threadsBean.getThreadCpuTime(tid);
|
||||||
|
t0 = (Long) previous.computeIfAbsent(tid, id -> new HashMap<>()).getOrDefault(STAT_CPU_TIME, t1);
|
||||||
|
t.put(STAT_CPU_TIME, t1);
|
||||||
|
t.put(STAT_CPU_TIME_PERC, (delta != 0) ? (t1 - t0) / ((double) delta * 1000000) : 0.0d);
|
||||||
|
// User
|
||||||
|
t1 = threadsBean.getThreadUserTime(tid);
|
||||||
|
t0 = (Long) previous.computeIfAbsent(tid, id -> new HashMap<>()).getOrDefault(STAT_USER_TIME, t1);
|
||||||
|
t.put(STAT_USER_TIME, t1);
|
||||||
|
t.put(STAT_USER_TIME_PERC, (delta != 0) ? (t1 - t0) / ((double) delta * 1000000) : 0.0d);
|
||||||
|
}
|
||||||
|
threads.add(t);
|
||||||
|
}
|
||||||
|
return threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void align(AttributedStringBuilder sb, String val, int width, Align align) {
|
||||||
|
if (align == Align.Left) {
|
||||||
|
sb.append(val);
|
||||||
|
for (int i = 0; i < width - val.length(); i++) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < width - val.length(); i++) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void display() throws IOException {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
display.resize(size.getRows(), size.getColumns());
|
||||||
|
|
||||||
|
List<AttributedString> lines = new ArrayList<>();
|
||||||
|
AttributedStringBuilder sb = new AttributedStringBuilder(size.getColumns());
|
||||||
|
|
||||||
|
// Top headers
|
||||||
|
sb.style(sb.style().bold());
|
||||||
|
sb.append("ttop");
|
||||||
|
sb.style(sb.style().boldOff());
|
||||||
|
sb.append(" - ");
|
||||||
|
sb.append(String.format("%8tT", new Date()));
|
||||||
|
sb.append(".");
|
||||||
|
|
||||||
|
OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
|
||||||
|
String osinfo = "OS: " + os.getName() + " " + os.getVersion() + ", " + os.getArch() + ", "
|
||||||
|
+ os.getAvailableProcessors() + " cpus.";
|
||||||
|
if (sb.length() + 1 + osinfo.length() < size.getColumns()) {
|
||||||
|
sb.append(" ");
|
||||||
|
} else {
|
||||||
|
lines.add(sb.toAttributedString());
|
||||||
|
sb.setLength(0);
|
||||||
|
}
|
||||||
|
sb.append(osinfo);
|
||||||
|
|
||||||
|
ClassLoadingMXBean cl = ManagementFactory.getClassLoadingMXBean();
|
||||||
|
String clsinfo = "Classes: " + cl.getLoadedClassCount() + " loaded, " + cl.getUnloadedClassCount()
|
||||||
|
+ " unloaded, " + cl.getTotalLoadedClassCount() + " loaded total.";
|
||||||
|
if (sb.length() + 1 + clsinfo.length() < size.getColumns()) {
|
||||||
|
sb.append(" ");
|
||||||
|
} else {
|
||||||
|
lines.add(sb.toAttributedString());
|
||||||
|
sb.setLength(0);
|
||||||
|
}
|
||||||
|
sb.append(clsinfo);
|
||||||
|
|
||||||
|
ThreadMXBean th = ManagementFactory.getThreadMXBean();
|
||||||
|
String thinfo = "Threads: " + th.getThreadCount() + ", peak: " + th.getPeakThreadCount() + ", started: "
|
||||||
|
+ th.getTotalStartedThreadCount() + ".";
|
||||||
|
if (sb.length() + 1 + thinfo.length() < size.getColumns()) {
|
||||||
|
sb.append(" ");
|
||||||
|
} else {
|
||||||
|
lines.add(sb.toAttributedString());
|
||||||
|
sb.setLength(0);
|
||||||
|
}
|
||||||
|
sb.append(thinfo);
|
||||||
|
|
||||||
|
MemoryMXBean me = ManagementFactory.getMemoryMXBean();
|
||||||
|
String meinfo = "Memory: " + "heap: "
|
||||||
|
+ memory(
|
||||||
|
me.getHeapMemoryUsage().getUsed(),
|
||||||
|
me.getHeapMemoryUsage().getMax()) + ", non heap: "
|
||||||
|
+ memory(
|
||||||
|
me.getNonHeapMemoryUsage().getUsed(),
|
||||||
|
me.getNonHeapMemoryUsage().getMax()) + ".";
|
||||||
|
if (sb.length() + 1 + meinfo.length() < size.getColumns()) {
|
||||||
|
sb.append(" ");
|
||||||
|
} else {
|
||||||
|
lines.add(sb.toAttributedString());
|
||||||
|
sb.setLength(0);
|
||||||
|
}
|
||||||
|
sb.append(meinfo);
|
||||||
|
|
||||||
|
StringBuilder sbc = new StringBuilder();
|
||||||
|
sbc.append("GC: ");
|
||||||
|
boolean first = true;
|
||||||
|
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
sbc.append(", ");
|
||||||
|
}
|
||||||
|
long count = gc.getCollectionCount();
|
||||||
|
long time = gc.getCollectionTime();
|
||||||
|
sbc.append(gc.getName())
|
||||||
|
.append(": ")
|
||||||
|
.append(count)
|
||||||
|
.append(" col. / ")
|
||||||
|
.append(String.format("%d", time / 1000))
|
||||||
|
.append(".")
|
||||||
|
.append(String.format("%03d", time % 1000))
|
||||||
|
.append(" s");
|
||||||
|
}
|
||||||
|
sbc.append(".");
|
||||||
|
if (sb.length() + 1 + sbc.length() < size.getColumns()) {
|
||||||
|
sb.append(" ");
|
||||||
|
} else {
|
||||||
|
lines.add(sb.toAttributedString());
|
||||||
|
sb.setLength(0);
|
||||||
|
}
|
||||||
|
sb.append(sbc);
|
||||||
|
lines.add(sb.toAttributedString());
|
||||||
|
sb.setLength(0);
|
||||||
|
|
||||||
|
lines.add(sb.toAttributedString());
|
||||||
|
|
||||||
|
// Threads
|
||||||
|
List<Map<String, Comparable<?>>> threads = infos();
|
||||||
|
Collections.sort(threads, comparator);
|
||||||
|
int nb = Math.min(size.getRows() - lines.size() - 2, nthreads > 0 ? nthreads : threads.size());
|
||||||
|
// Compute values
|
||||||
|
List<Map<String, String>> values = threads.subList(0, nb).stream()
|
||||||
|
.map(thread -> stats.stream().collect(Collectors.toMap(Function.identity(), key -> columns.get(key)
|
||||||
|
.format
|
||||||
|
.apply(thread.get(key)))))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
for (String key : stats) {
|
||||||
|
int width =
|
||||||
|
values.stream().mapToInt(map -> map.get(key).length()).max().orElse(0);
|
||||||
|
widths.put(key, Math.max(columns.get(key).header.length(), Math.max(width, widths.getOrDefault(key, 0))));
|
||||||
|
}
|
||||||
|
List<String> cstats;
|
||||||
|
if (widths.values().stream().mapToInt(Integer::intValue).sum() + stats.size() - 1 < size.getColumns()) {
|
||||||
|
cstats = stats;
|
||||||
|
} else {
|
||||||
|
cstats = new ArrayList<>();
|
||||||
|
int sz = 0;
|
||||||
|
for (String stat : stats) {
|
||||||
|
int nsz = sz;
|
||||||
|
if (nsz > 0) {
|
||||||
|
nsz++;
|
||||||
|
}
|
||||||
|
nsz += widths.get(stat);
|
||||||
|
if (nsz < size.getColumns()) {
|
||||||
|
sz = nsz;
|
||||||
|
cstats.add(stat);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Headers
|
||||||
|
for (String key : cstats) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(" ");
|
||||||
|
}
|
||||||
|
Column col = columns.get(key);
|
||||||
|
align(sb, col.header, widths.get(key), col.align);
|
||||||
|
}
|
||||||
|
lines.add(sb.toAttributedString());
|
||||||
|
sb.setLength(0);
|
||||||
|
// Threads
|
||||||
|
for (int i = 0; i < nb; i++) {
|
||||||
|
Map<String, Comparable<?>> thread = threads.get(i);
|
||||||
|
long tid = (Long) thread.get(STAT_TID);
|
||||||
|
for (String key : cstats) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(" ");
|
||||||
|
}
|
||||||
|
long last;
|
||||||
|
Object cur = thread.get(key);
|
||||||
|
Object prv =
|
||||||
|
previous.computeIfAbsent(tid, id -> new HashMap<>()).put(key, cur);
|
||||||
|
if (prv != null && !prv.equals(cur)) {
|
||||||
|
changes.computeIfAbsent(tid, id -> new HashMap<>()).put(key, now);
|
||||||
|
last = now;
|
||||||
|
} else {
|
||||||
|
last = changes.computeIfAbsent(tid, id -> new HashMap<>()).getOrDefault(key, 0L);
|
||||||
|
}
|
||||||
|
long fade = delay * 24;
|
||||||
|
if (now - last < fade) {
|
||||||
|
int r = (int) ((now - last) / (fade / 24));
|
||||||
|
sb.style(sb.style().foreground(255 - r).background(9));
|
||||||
|
}
|
||||||
|
align(sb, values.get(i).get(key), widths.get(key), columns.get(key).align);
|
||||||
|
sb.style(sb.style().backgroundOff().foregroundOff());
|
||||||
|
}
|
||||||
|
lines.add(sb.toAttributedString());
|
||||||
|
sb.setLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
display.update(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comparator<Map<String, Comparable<?>>> buildComparator(List<String> sort) {
|
||||||
|
if (sort == null || sort.isEmpty()) {
|
||||||
|
sort = Collections.singletonList(STAT_TID);
|
||||||
|
}
|
||||||
|
Comparator<Map<String, Comparable<?>>> comparator = null;
|
||||||
|
for (String key : sort) {
|
||||||
|
String fkey;
|
||||||
|
boolean asc;
|
||||||
|
if (key.startsWith("+")) {
|
||||||
|
fkey = key.substring(1);
|
||||||
|
asc = true;
|
||||||
|
} else if (key.startsWith("-")) {
|
||||||
|
fkey = key.substring(1);
|
||||||
|
asc = false;
|
||||||
|
} else {
|
||||||
|
fkey = key;
|
||||||
|
asc = true;
|
||||||
|
}
|
||||||
|
if (!columns.containsKey(fkey)) {
|
||||||
|
throw new IllegalArgumentException("Unsupported sort key: " + fkey);
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Comparator<Map<String, Comparable<?>>> comp = Comparator.comparing(m -> (Comparable) m.get(fkey));
|
||||||
|
if (asc) {
|
||||||
|
comp = comp.reversed();
|
||||||
|
}
|
||||||
|
if (comparator != null) {
|
||||||
|
comparator = comparator.thenComparing(comp);
|
||||||
|
} else {
|
||||||
|
comparator = comp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return comparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(String name, Align align, String header, Function<Object, String> format) {
|
||||||
|
columns.put(name, new Column(name, align, header, format));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is for long running commands to be interrupted by ctrl-c
|
||||||
|
*/
|
||||||
|
private void checkInterrupted() throws InterruptedException {
|
||||||
|
Thread.yield();
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindKeys(KeyMap<Operation> map) {
|
||||||
|
map.bind(Operation.HELP, "h", "?");
|
||||||
|
map.bind(Operation.EXIT, "q", ":q", "Q", ":Q", "ZZ");
|
||||||
|
map.bind(Operation.INCREASE_DELAY, "+");
|
||||||
|
map.bind(Operation.DECREASE_DELAY, "-");
|
||||||
|
map.bind(Operation.CLEAR, KeyMap.ctrl('L'));
|
||||||
|
map.bind(Operation.REVERSE, "r");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Align {
|
||||||
|
Left,
|
||||||
|
Right
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Operation {
|
||||||
|
INCREASE_DELAY,
|
||||||
|
DECREASE_DELAY,
|
||||||
|
HELP,
|
||||||
|
EXIT,
|
||||||
|
CLEAR,
|
||||||
|
REVERSE
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Column {
|
||||||
|
final String name;
|
||||||
|
final Align align;
|
||||||
|
final String header;
|
||||||
|
final Function<Object, String> format;
|
||||||
|
|
||||||
|
Column(String name, Align align, String header, Function<Object, String> format) {
|
||||||
|
this.name = name;
|
||||||
|
this.align = align;
|
||||||
|
this.header = header;
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2095
net-cli/src/main/java/org/jline/builtins/Tmux.java
Normal file
2095
net-cli/src/main/java/org/jline/builtins/Tmux.java
Normal file
File diff suppressed because it is too large
Load diff
46
net-cli/src/main/java/org/jline/console/ArgDesc.java
Normal file
46
net-cli/src/main/java/org/jline/console/ArgDesc.java
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
|
||||||
|
public class ArgDesc {
|
||||||
|
private final String name;
|
||||||
|
private final List<AttributedString> description;
|
||||||
|
|
||||||
|
public ArgDesc(String name) {
|
||||||
|
this(name, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgDesc(String name, List<AttributedString> description) {
|
||||||
|
if (name.contains("\t") || name.contains(" ")) {
|
||||||
|
throw new IllegalArgumentException("Bad argument name: " + name);
|
||||||
|
}
|
||||||
|
this.name = name;
|
||||||
|
this.description = new ArrayList<>(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ArgDesc> doArgNames(List<String> names) {
|
||||||
|
List<ArgDesc> out = new ArrayList<>();
|
||||||
|
for (String n : names) {
|
||||||
|
out.add(new ArgDesc(n));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AttributedString> getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
132
net-cli/src/main/java/org/jline/console/CmdDesc.java
Normal file
132
net-cli/src/main/java/org/jline/console/CmdDesc.java
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
|
||||||
|
public class CmdDesc {
|
||||||
|
private List<AttributedString> mainDesc;
|
||||||
|
private List<ArgDesc> argsDesc;
|
||||||
|
private TreeMap<String, List<AttributedString>> optsDesc;
|
||||||
|
private Pattern errorPattern;
|
||||||
|
private int errorIndex = -1;
|
||||||
|
private boolean valid = true;
|
||||||
|
private boolean command = false;
|
||||||
|
private boolean subcommand = false;
|
||||||
|
private boolean highlighted = true;
|
||||||
|
|
||||||
|
public CmdDesc() {
|
||||||
|
command = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CmdDesc(boolean valid) {
|
||||||
|
this.valid = valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CmdDesc(List<ArgDesc> argsDesc) {
|
||||||
|
this(new ArrayList<>(), argsDesc, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CmdDesc(List<ArgDesc> argsDesc, Map<String, List<AttributedString>> optsDesc) {
|
||||||
|
this(new ArrayList<>(), argsDesc, optsDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CmdDesc(
|
||||||
|
List<AttributedString> mainDesc, List<ArgDesc> argsDesc, Map<String, List<AttributedString>> optsDesc) {
|
||||||
|
this.argsDesc = new ArrayList<>(argsDesc);
|
||||||
|
this.optsDesc = new TreeMap<>(optsDesc);
|
||||||
|
if (mainDesc.isEmpty() && optsDesc.containsKey("main")) {
|
||||||
|
this.mainDesc = new ArrayList<>(optsDesc.get("main"));
|
||||||
|
this.optsDesc.remove("main");
|
||||||
|
} else {
|
||||||
|
this.mainDesc = new ArrayList<>(mainDesc);
|
||||||
|
}
|
||||||
|
this.command = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCommand() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSubcommand() {
|
||||||
|
return subcommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubcommand(boolean subcommand) {
|
||||||
|
this.subcommand = subcommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHighlighted() {
|
||||||
|
return highlighted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHighlighted(boolean highlighted) {
|
||||||
|
this.highlighted = highlighted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CmdDesc mainDesc(List<AttributedString> mainDesc) {
|
||||||
|
this.mainDesc = new ArrayList<>(mainDesc);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AttributedString> getMainDesc() {
|
||||||
|
return mainDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMainDesc(List<AttributedString> mainDesc) {
|
||||||
|
this.mainDesc = new ArrayList<>(mainDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeMap<String, List<AttributedString>> getOptsDesc() {
|
||||||
|
return optsDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pattern getErrorPattern() {
|
||||||
|
return errorPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorPattern(Pattern errorPattern) {
|
||||||
|
this.errorPattern = errorPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrorIndex() {
|
||||||
|
return errorIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorIndex(int errorIndex) {
|
||||||
|
this.errorIndex = errorIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ArgDesc> getArgsDesc() {
|
||||||
|
return argsDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean optionWithValue(String option) {
|
||||||
|
for (String key : optsDesc.keySet()) {
|
||||||
|
if (key.matches("(^|.*\\s)" + option + "($|=.*|\\s.*)")) {
|
||||||
|
return key.contains("=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedString optionDescription(String key) {
|
||||||
|
return optsDesc.get(key).size() > 0 ? optsDesc.get(key).get(0) : new AttributedString("");
|
||||||
|
}
|
||||||
|
}
|
72
net-cli/src/main/java/org/jline/console/CmdLine.java
Normal file
72
net-cli/src/main/java/org/jline/console/CmdLine.java
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CmdLine {
|
||||||
|
private final String line;
|
||||||
|
private final String head;
|
||||||
|
private final String tail;
|
||||||
|
private final List<String> args;
|
||||||
|
private final DescriptionType descType;
|
||||||
|
/**
|
||||||
|
* CmdLine class constructor.
|
||||||
|
*
|
||||||
|
* @param line Command line
|
||||||
|
* @param head Command line till cursor, method parameters and opening parenthesis before the cursor are removed.
|
||||||
|
* @param tail Command line after cursor, method parameters and closing parenthesis after the cursor are removed.
|
||||||
|
* @param args Parsed command line arguments.
|
||||||
|
* @param descType Request COMMAND, METHOD or SYNTAX description
|
||||||
|
*/
|
||||||
|
public CmdLine(String line, String head, String tail, List<String> args, DescriptionType descType) {
|
||||||
|
this.line = line;
|
||||||
|
this.head = head;
|
||||||
|
this.tail = tail;
|
||||||
|
this.args = new ArrayList<>(args);
|
||||||
|
this.descType = descType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLine() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHead() {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTail() {
|
||||||
|
return tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DescriptionType getDescriptionType() {
|
||||||
|
return descType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DescriptionType {
|
||||||
|
/**
|
||||||
|
* Cursor is at the end of line. The args[0] is completed, the line does not have unclosed opening parenthesis
|
||||||
|
* and does not end to the closing parenthesis.
|
||||||
|
*/
|
||||||
|
COMMAND,
|
||||||
|
/**
|
||||||
|
* The part of the line from beginning till cursor has unclosed opening parenthesis.
|
||||||
|
*/
|
||||||
|
METHOD,
|
||||||
|
/**
|
||||||
|
* The part of the line from beginning till cursor ends to the closing parenthesis.
|
||||||
|
*/
|
||||||
|
SYNTAX
|
||||||
|
}
|
||||||
|
}
|
75
net-cli/src/main/java/org/jline/console/CommandInput.java
Normal file
75
net-cli/src/main/java/org/jline/console/CommandInput.java
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
|
||||||
|
public class CommandInput {
|
||||||
|
String command;
|
||||||
|
String[] args;
|
||||||
|
Object[] xargs;
|
||||||
|
Terminal terminal;
|
||||||
|
InputStream in;
|
||||||
|
PrintStream out;
|
||||||
|
PrintStream err;
|
||||||
|
|
||||||
|
public CommandInput(String command, Object[] xargs, CommandRegistry.CommandSession session) {
|
||||||
|
if (xargs != null) {
|
||||||
|
this.xargs = xargs;
|
||||||
|
this.args = new String[xargs.length];
|
||||||
|
for (int i = 0; i < xargs.length; i++) {
|
||||||
|
this.args[i] = xargs[i] != null ? xargs[i].toString() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.command = command;
|
||||||
|
this.terminal = session.terminal();
|
||||||
|
this.in = session.in();
|
||||||
|
this.out = session.out();
|
||||||
|
this.err = session.err();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandInput(
|
||||||
|
String command, Object[] args, Terminal terminal, InputStream in, PrintStream out, PrintStream err) {
|
||||||
|
this(command, args, new CommandRegistry.CommandSession(terminal, in, out, err));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String command() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] args() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] xargs() {
|
||||||
|
return xargs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Terminal terminal() {
|
||||||
|
return terminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream in() {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintStream out() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintStream err() {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandRegistry.CommandSession session() {
|
||||||
|
return new CommandRegistry.CommandSession(terminal, in, out, err);
|
||||||
|
}
|
||||||
|
}
|
40
net-cli/src/main/java/org/jline/console/CommandMethods.java
Normal file
40
net-cli/src/main/java/org/jline/console/CommandMethods.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
|
||||||
|
public class CommandMethods {
|
||||||
|
Function<CommandInput, ?> execute;
|
||||||
|
Function<String, List<Completer>> compileCompleter;
|
||||||
|
|
||||||
|
public CommandMethods(Function<CommandInput, ?> execute, Function<String, List<Completer>> compileCompleter) {
|
||||||
|
this.execute = execute;
|
||||||
|
this.compileCompleter = compileCompleter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandMethods(Consumer<CommandInput> execute, Function<String, List<Completer>> compileCompleter) {
|
||||||
|
this.execute = (CommandInput i) -> {
|
||||||
|
execute.accept(i);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
this.compileCompleter = compileCompleter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function<CommandInput, ?> execute() {
|
||||||
|
return execute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function<String, List<Completer>> compileCompleter() {
|
||||||
|
return compileCompleter;
|
||||||
|
}
|
||||||
|
}
|
163
net-cli/src/main/java/org/jline/console/CommandRegistry.java
Normal file
163
net-cli/src/main/java/org/jline/console/CommandRegistry.java
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.jline.reader.impl.completer.SystemCompleter;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store command information, compile tab completers and execute registered commands.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public interface CommandRegistry {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregate SystemCompleters of commandRegisteries
|
||||||
|
*
|
||||||
|
* @param commandRegistries command registeries which completers is to be aggregated
|
||||||
|
* @return uncompiled SystemCompleter
|
||||||
|
*/
|
||||||
|
static SystemCompleter aggregateCompleters(CommandRegistry... commandRegistries) {
|
||||||
|
SystemCompleter out = new SystemCompleter();
|
||||||
|
for (CommandRegistry r : commandRegistries) {
|
||||||
|
out.add(r.compileCompleters());
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregate and compile SystemCompleters of commandRegisteries
|
||||||
|
*
|
||||||
|
* @param commandRegistries command registeries which completers is to be aggregated and compile
|
||||||
|
* @return compiled SystemCompleter
|
||||||
|
*/
|
||||||
|
static SystemCompleter compileCompleters(CommandRegistry... commandRegistries) {
|
||||||
|
SystemCompleter out = aggregateCompleters(commandRegistries);
|
||||||
|
out.compile();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of this registry.
|
||||||
|
*
|
||||||
|
* @return the name of the registry
|
||||||
|
*/
|
||||||
|
default String name() {
|
||||||
|
return this.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the command names known by this registry.
|
||||||
|
*
|
||||||
|
* @return the set of known command names, excluding aliases
|
||||||
|
*/
|
||||||
|
Set<String> commandNames();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of alias-to-command names known by this registry.
|
||||||
|
*
|
||||||
|
* @return a map with alias keys and command name values
|
||||||
|
*/
|
||||||
|
Map<String, String> commandAliases();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a short info about command known by this registry.
|
||||||
|
*
|
||||||
|
* @param command the command name
|
||||||
|
* @return a short info about command
|
||||||
|
*/
|
||||||
|
List<String> commandInfo(String command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a command with the specified name is known to this registry.
|
||||||
|
*
|
||||||
|
* @param command the command name to test
|
||||||
|
* @return true if the specified command is registered
|
||||||
|
*/
|
||||||
|
boolean hasCommand(String command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@code SystemCompleter} that can provide detailed completion
|
||||||
|
* information for all registered commands.
|
||||||
|
*
|
||||||
|
* @return a SystemCompleter that can provide command completion for all registered commands
|
||||||
|
*/
|
||||||
|
SystemCompleter compileCompleters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a command description for use in the JLine Widgets framework.
|
||||||
|
* Default method must be overridden to return sub command descriptions.
|
||||||
|
*
|
||||||
|
* @param args command (args[0]) and its arguments
|
||||||
|
* @return command description for JLine TailTipWidgets to be displayed
|
||||||
|
* in the terminal status bar.
|
||||||
|
*/
|
||||||
|
CmdDesc commandDescription(List<String> args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a command.
|
||||||
|
*
|
||||||
|
* @param session the data of the current command session
|
||||||
|
* @param command the name of the command
|
||||||
|
* @param args arguments of the command
|
||||||
|
* @return result of the command execution
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
default Object invoke(CommandSession session, String command, Object... args) throws Exception {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"CommandRegistry method invoke(session, command, ... args) is not implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommandSession {
|
||||||
|
private final Terminal terminal;
|
||||||
|
private final InputStream in;
|
||||||
|
private final PrintStream out;
|
||||||
|
private final PrintStream err;
|
||||||
|
|
||||||
|
public CommandSession() {
|
||||||
|
this.in = System.in;
|
||||||
|
this.out = System.out;
|
||||||
|
this.err = System.err;
|
||||||
|
this.terminal = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandSession(Terminal terminal) {
|
||||||
|
this(terminal, terminal.input(), new PrintStream(terminal.output()), new PrintStream(terminal.output()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandSession(Terminal terminal, InputStream in, PrintStream out, PrintStream err) {
|
||||||
|
this.terminal = terminal;
|
||||||
|
this.in = in;
|
||||||
|
this.out = out;
|
||||||
|
this.err = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Terminal terminal() {
|
||||||
|
return terminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream in() {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintStream out() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintStream err() {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
318
net-cli/src/main/java/org/jline/console/ConsoleEngine.java
Normal file
318
net-cli/src/main/java/org/jline/console/ConsoleEngine.java
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2023, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.Widget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage console variables, commands and script executions.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public interface ConsoleEngine extends CommandRegistry {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console string variable of nanorc file full path
|
||||||
|
*/
|
||||||
|
String VAR_NANORC = "NANORC";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the command name first character if it is colon
|
||||||
|
*
|
||||||
|
* @param command the name of the command to complete
|
||||||
|
* @return command name without starting colon
|
||||||
|
*/
|
||||||
|
static String plainCommand(String command) {
|
||||||
|
return command.startsWith(":") ? command.substring(1) : command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets lineReader
|
||||||
|
*
|
||||||
|
* @param reader LineReader
|
||||||
|
*/
|
||||||
|
void setLineReader(LineReader reader);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets systemRegistry
|
||||||
|
*
|
||||||
|
* @param systemRegistry SystemRegistry
|
||||||
|
*/
|
||||||
|
void setSystemRegistry(SystemRegistry systemRegistry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substituting args references with their values.
|
||||||
|
*
|
||||||
|
* @param args the arguments to be expanded
|
||||||
|
* @return expanded arguments
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
Object[] expandParameters(String[] args) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substitutes command line with system registry invoke method call.
|
||||||
|
*
|
||||||
|
* @param line command line to be expanded
|
||||||
|
* @return expanded command line
|
||||||
|
*/
|
||||||
|
String expandCommandLine(String line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands parameter list to string
|
||||||
|
*
|
||||||
|
* @param params list of script parameters
|
||||||
|
* @return expanded parameters list
|
||||||
|
*/
|
||||||
|
String expandToList(List<String> params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all scripts found from PATH
|
||||||
|
*
|
||||||
|
* @return map keys have script file names and value is true if it is console script
|
||||||
|
*/
|
||||||
|
Map<String, Boolean> scripts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets file name extension used by console scripts
|
||||||
|
*
|
||||||
|
* @param extension console script file extension
|
||||||
|
*/
|
||||||
|
void setScriptExtension(String extension);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if alias 'name' exists
|
||||||
|
*
|
||||||
|
* @param name alias name
|
||||||
|
* @return true if alias exists
|
||||||
|
*/
|
||||||
|
boolean hasAlias(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns alias 'name' value
|
||||||
|
*
|
||||||
|
* @param name alias name
|
||||||
|
* @return value of alias
|
||||||
|
*/
|
||||||
|
String getAlias(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns defined pipes
|
||||||
|
*
|
||||||
|
* @return map of defined pipes
|
||||||
|
*/
|
||||||
|
Map<String, List<String>> getPipes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns named pipe names
|
||||||
|
*
|
||||||
|
* @return list of named pipe names
|
||||||
|
*/
|
||||||
|
List<String> getNamedPipes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns script and variable completers
|
||||||
|
*
|
||||||
|
* @return script and variable completers
|
||||||
|
*/
|
||||||
|
List<Completer> scriptCompleters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist object to file
|
||||||
|
*
|
||||||
|
* @param file file where object should be written
|
||||||
|
* @param object object to persist
|
||||||
|
*/
|
||||||
|
void persist(Path file, Object object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read object from file
|
||||||
|
*
|
||||||
|
* @param file file from where object should be read
|
||||||
|
* @return object
|
||||||
|
* @throws IOException in case of error
|
||||||
|
*/
|
||||||
|
Object slurp(Path file) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read console option value
|
||||||
|
*
|
||||||
|
* @param <T> option type
|
||||||
|
* @param option option name
|
||||||
|
* @param defval default value
|
||||||
|
* @return option value
|
||||||
|
*/
|
||||||
|
<T> T consoleOption(String option, T defval);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set console option value
|
||||||
|
*
|
||||||
|
* @param name the option name
|
||||||
|
* @param value value to assign console option
|
||||||
|
*/
|
||||||
|
void setConsoleOption(String name, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes command line that does not contain known command by the system registry.
|
||||||
|
* If the line is neither JLine or ScriptEngine script it will be evaluated
|
||||||
|
* as ScriptEngine statement.
|
||||||
|
*
|
||||||
|
* @param name parsed command/script name
|
||||||
|
* @param rawLine raw command line
|
||||||
|
* @param args parsed arguments of the command
|
||||||
|
* @return command line execution result
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
Object execute(String name, String rawLine, String[] args) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes either JLine or ScriptEngine script.
|
||||||
|
*
|
||||||
|
* @param script script file
|
||||||
|
* @return script execution result
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
default Object execute(File script) throws Exception {
|
||||||
|
return execute(script, "", new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes either JLine or ScriptEngine script.
|
||||||
|
*
|
||||||
|
* @param script script file
|
||||||
|
* @param rawLine raw command line
|
||||||
|
* @param args script arguments
|
||||||
|
* @return script execution result
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
Object execute(File script, String rawLine, String[] args) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post processes execution result. If result is to be assigned to the console variable
|
||||||
|
* then method will return null.
|
||||||
|
*
|
||||||
|
* @param line command line
|
||||||
|
* @param result command result to process
|
||||||
|
* @param output command redirected output
|
||||||
|
* @return processed result
|
||||||
|
*/
|
||||||
|
ExecutionResult postProcess(String line, Object result, String output);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post processes execution result.
|
||||||
|
*
|
||||||
|
* @param result command result to process
|
||||||
|
* @return processed result
|
||||||
|
*/
|
||||||
|
ExecutionResult postProcess(Object result);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print object if trace is enabled
|
||||||
|
*
|
||||||
|
* @param object object to print
|
||||||
|
*/
|
||||||
|
void trace(Object object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print object.
|
||||||
|
*
|
||||||
|
* @param object object to print
|
||||||
|
*/
|
||||||
|
void println(Object object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create console variable
|
||||||
|
*
|
||||||
|
* @param name name of the variable
|
||||||
|
* @param value value of the variable
|
||||||
|
*/
|
||||||
|
void putVariable(String name, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get variable value
|
||||||
|
*
|
||||||
|
* @param name name of the variable
|
||||||
|
* @return variable value
|
||||||
|
*/
|
||||||
|
Object getVariable(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if variable with name exists
|
||||||
|
*
|
||||||
|
* @param name name of the variable
|
||||||
|
* @return true if variable with name exists
|
||||||
|
*/
|
||||||
|
boolean hasVariable(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete temporary console variables
|
||||||
|
*/
|
||||||
|
void purge();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute widget function
|
||||||
|
*
|
||||||
|
* @param function to execute
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
boolean executeWidget(Object function);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if consoleEngine is executing script
|
||||||
|
*
|
||||||
|
* @return true when executing script
|
||||||
|
*/
|
||||||
|
boolean isExecuting();
|
||||||
|
|
||||||
|
class ExecutionResult {
|
||||||
|
final int status;
|
||||||
|
final Object result;
|
||||||
|
|
||||||
|
public ExecutionResult(int status, Object result) {
|
||||||
|
this.status = status;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int status() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object result() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WidgetCreator implements Widget {
|
||||||
|
private final ConsoleEngine consoleEngine;
|
||||||
|
private final Object function;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public WidgetCreator(ConsoleEngine consoleEngine, String function) {
|
||||||
|
this.consoleEngine = consoleEngine;
|
||||||
|
this.name = function;
|
||||||
|
this.function = consoleEngine.getVariable(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply() {
|
||||||
|
return consoleEngine.executeWidget(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
228
net-cli/src/main/java/org/jline/console/Printer.java
Normal file
228
net-cli/src/main/java/org/jline/console/Printer.java
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2022, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print object to the console.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public interface Printer {
|
||||||
|
/**
|
||||||
|
* Value: Boolean<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* Ignore columnsOut configuration.
|
||||||
|
*/
|
||||||
|
String ALL = "all";
|
||||||
|
//
|
||||||
|
// option names
|
||||||
|
//
|
||||||
|
// 1) command options
|
||||||
|
//
|
||||||
|
/**
|
||||||
|
* Value: {@code List<String>}<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Display given keys/columns on map/table.
|
||||||
|
*/
|
||||||
|
String COLUMNS = "columns";
|
||||||
|
/**
|
||||||
|
* Value: {@code List<String>}<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* Exclude given columns on table.
|
||||||
|
*/
|
||||||
|
String EXCLUDE = "exclude";
|
||||||
|
/**
|
||||||
|
* Value: {@code List<String>}<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* Include given columns on table.
|
||||||
|
*/
|
||||||
|
String INCLUDE = "include";
|
||||||
|
/**
|
||||||
|
* Value: Integer<br>
|
||||||
|
* Applies: MAP<br>
|
||||||
|
* Indention size.
|
||||||
|
*/
|
||||||
|
String INDENTION = "indention";
|
||||||
|
/**
|
||||||
|
* Value: Integer<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Maximum column width.
|
||||||
|
*/
|
||||||
|
String MAX_COLUMN_WIDTH = "maxColumnWidth";
|
||||||
|
/**
|
||||||
|
* Value: Integer<br>
|
||||||
|
* Applies: MAP<br>
|
||||||
|
* Maximum depth objects are resolved.
|
||||||
|
*/
|
||||||
|
String MAX_DEPTH = "maxDepth";
|
||||||
|
/**
|
||||||
|
* Value: Integer<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Maximum number of lines to display.
|
||||||
|
*/
|
||||||
|
String MAXROWS = "maxrows";
|
||||||
|
/**
|
||||||
|
* Value: Boolean<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* Display one row data on table.
|
||||||
|
*/
|
||||||
|
String ONE_ROW_TABLE = "oneRowTable";
|
||||||
|
/**
|
||||||
|
* Value: Boolean<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* Display table row numbers.
|
||||||
|
*/
|
||||||
|
String ROWNUM = "rownum";
|
||||||
|
/**
|
||||||
|
* Value: Boolean<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* Truncate table column names: property.field to field.
|
||||||
|
*/
|
||||||
|
String SHORT_NAMES = "shortNames";
|
||||||
|
/**
|
||||||
|
* Value: Boolean<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Ignore all options defined in PRNT_OPTIONS.
|
||||||
|
*/
|
||||||
|
String SKIP_DEFAULT_OPTIONS = "skipDefaultOptions";
|
||||||
|
/**
|
||||||
|
* Value: Boolean<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* Display object structures and lists on table.
|
||||||
|
*/
|
||||||
|
String STRUCT_ON_TABLE = "structsOnTable";
|
||||||
|
/**
|
||||||
|
* Value: String<br>
|
||||||
|
* Use nanorc STYLE<br>
|
||||||
|
*/
|
||||||
|
String STYLE = "style";
|
||||||
|
/**
|
||||||
|
* Value: Boolean<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Use object's toString() method to get print value
|
||||||
|
* DEFAULT: object's fields are put to property map before printing
|
||||||
|
*/
|
||||||
|
String TO_STRING = "toString";
|
||||||
|
/**
|
||||||
|
* Value: String<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Nanorc syntax style used to highlight values.
|
||||||
|
*/
|
||||||
|
String VALUE_STYLE = "valueStyle";
|
||||||
|
/**
|
||||||
|
* Value: Integer<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Display width (default terminal width).
|
||||||
|
*/
|
||||||
|
String WIDTH = "width";
|
||||||
|
/**
|
||||||
|
* Value: String<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* Table cell vertical border character.
|
||||||
|
*/
|
||||||
|
String BORDER = "border";
|
||||||
|
/**
|
||||||
|
* Value: TableRows<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* Highlight table rows.
|
||||||
|
*/
|
||||||
|
String ROW_HIGHLIGHT = "rowHighlight";
|
||||||
|
/**
|
||||||
|
* Value: {@code List<String>}<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* These map values will be added to the table before all the other keys.
|
||||||
|
*/
|
||||||
|
String COLUMNS_IN = "columnsIn";
|
||||||
|
//
|
||||||
|
// 2) additional PRNT_OPTIONS
|
||||||
|
//
|
||||||
|
/**
|
||||||
|
* Value: {@code List<String>}<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* These map values will not be inserted to the table.
|
||||||
|
*/
|
||||||
|
String COLUMNS_OUT = "columnsOut";
|
||||||
|
/**
|
||||||
|
* Value: {@code Map<regex, function>}.<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* If command result map key matches with regex the highlight function is applied
|
||||||
|
* to the corresponding map value. The regex = * is processed after all the other regexes and the highlight
|
||||||
|
* function will be applied to all map values that have not been already highlighted.
|
||||||
|
*/
|
||||||
|
String HIGHLIGHT_VALUE = "highlightValue";
|
||||||
|
/**
|
||||||
|
* Value: Double<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* default value 0.8 i.e. if at least of 4 of the 5 results map keys match with reference key set the
|
||||||
|
* result will be printed out as a table.
|
||||||
|
*/
|
||||||
|
String MAP_SIMILARITY = "mapSimilarity";
|
||||||
|
/**
|
||||||
|
* Value: {@code Map<class, function>}<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Overrides the ScriptEngine toMap() method.
|
||||||
|
*/
|
||||||
|
String OBJECT_TO_MAP = "objectToMap";
|
||||||
|
/**
|
||||||
|
* Value: {@code Map<class, function>}<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Overrides the ScriptEngine toString() method.
|
||||||
|
*/
|
||||||
|
String OBJECT_TO_STRING = "objectToString";
|
||||||
|
/**
|
||||||
|
* Value: Boolean<br>
|
||||||
|
* Applies: MAP and TABLE<br>
|
||||||
|
* Highlight everything also strings with spaces
|
||||||
|
* DEFAULT: highlight only strings without spaces or enclosed by quotes or brackets
|
||||||
|
*/
|
||||||
|
String VALUE_STYLE_ALL = "valueStyleAll";
|
||||||
|
/**
|
||||||
|
* Value: Boolean<br>
|
||||||
|
* Applies: TABLE<br>
|
||||||
|
* List the collection of simple values in multiple columns
|
||||||
|
* DEFAULT: list values in one column
|
||||||
|
*/
|
||||||
|
String MULTI_COLUMNS = "multiColumns";
|
||||||
|
List<String> BOOLEAN_KEYS = Arrays.asList(
|
||||||
|
ALL,
|
||||||
|
ONE_ROW_TABLE,
|
||||||
|
ROWNUM,
|
||||||
|
SHORT_NAMES,
|
||||||
|
SKIP_DEFAULT_OPTIONS,
|
||||||
|
STRUCT_ON_TABLE,
|
||||||
|
TO_STRING,
|
||||||
|
VALUE_STYLE_ALL,
|
||||||
|
MULTI_COLUMNS);
|
||||||
|
|
||||||
|
default void println(Object object) {
|
||||||
|
println(new HashMap<>(), object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(Map<String, Object> options, Object object);
|
||||||
|
|
||||||
|
default Exception prntCommand(CommandInput input) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear printer syntax highlighter cache
|
||||||
|
*/
|
||||||
|
boolean refresh();
|
||||||
|
|
||||||
|
enum TableRows {
|
||||||
|
EVEN,
|
||||||
|
ODD,
|
||||||
|
ALL
|
||||||
|
}
|
||||||
|
}
|
196
net-cli/src/main/java/org/jline/console/ScriptEngine.java
Normal file
196
net-cli/src/main/java/org/jline/console/ScriptEngine.java
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage scriptEngine variables, statements and script execution.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public interface ScriptEngine {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return scriptEngine name
|
||||||
|
*/
|
||||||
|
String getEngineName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return script file name extensions
|
||||||
|
*/
|
||||||
|
Collection<String> getExtensions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return script tab completer
|
||||||
|
*/
|
||||||
|
Completer getScriptCompleter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if console variable exists
|
||||||
|
*
|
||||||
|
* @param name variable name
|
||||||
|
* @return true if variable exists
|
||||||
|
*/
|
||||||
|
boolean hasVariable(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates variable
|
||||||
|
*
|
||||||
|
* @param name variable name
|
||||||
|
* @param value value
|
||||||
|
*/
|
||||||
|
void put(String name, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets variable value
|
||||||
|
*
|
||||||
|
* @param name variable name
|
||||||
|
* @return value of the variable
|
||||||
|
*/
|
||||||
|
Object get(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all variables with values
|
||||||
|
*
|
||||||
|
* @return map of the variables
|
||||||
|
*/
|
||||||
|
default Map<String, Object> find() {
|
||||||
|
return find(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the variables that match the name. Name can contain * wild cards.
|
||||||
|
*
|
||||||
|
* @param name variable name
|
||||||
|
* @return map the variables
|
||||||
|
*/
|
||||||
|
Map<String, Object> find(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes variables. Variable name can contain * wild cards.
|
||||||
|
*
|
||||||
|
* @param vars variables to be deleted
|
||||||
|
*/
|
||||||
|
void del(String... vars);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize object to JSON string.
|
||||||
|
*
|
||||||
|
* @param object object to serialize to JSON
|
||||||
|
* @return formatted JSON string
|
||||||
|
*/
|
||||||
|
String toJson(Object object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts object to string.
|
||||||
|
*
|
||||||
|
* @param object the object
|
||||||
|
* @return object string value
|
||||||
|
*/
|
||||||
|
String toString(Object object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts object fields to map.
|
||||||
|
*
|
||||||
|
* @param object the object
|
||||||
|
* @return object fields map
|
||||||
|
*/
|
||||||
|
Map<String, Object> toMap(Object object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize value
|
||||||
|
*
|
||||||
|
* @param value the value
|
||||||
|
* @return deserialized value
|
||||||
|
*/
|
||||||
|
default Object deserialize(String value) {
|
||||||
|
return deserialize(value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize value
|
||||||
|
*
|
||||||
|
* @param value the value
|
||||||
|
* @param format serialization format
|
||||||
|
* @return deserialized value
|
||||||
|
*/
|
||||||
|
Object deserialize(String value, String format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Supported serialization formats
|
||||||
|
*/
|
||||||
|
List<String> getSerializationFormats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Supported deserialization formats
|
||||||
|
*/
|
||||||
|
List<String> getDeserializationFormats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists object value to file.
|
||||||
|
*
|
||||||
|
* @param file file
|
||||||
|
* @param object object
|
||||||
|
*/
|
||||||
|
void persist(Path file, Object object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists object value to file.
|
||||||
|
*
|
||||||
|
* @param file the file
|
||||||
|
* @param object the object
|
||||||
|
* @param format serialization format
|
||||||
|
*/
|
||||||
|
void persist(Path file, Object object, String format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes scriptEngine statement
|
||||||
|
*
|
||||||
|
* @param statement the statement
|
||||||
|
* @return result
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
Object execute(String statement) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes scriptEngine script
|
||||||
|
*
|
||||||
|
* @param script the script
|
||||||
|
* @return result
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
default Object execute(File script) throws Exception {
|
||||||
|
return execute(script, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes scriptEngine script
|
||||||
|
*
|
||||||
|
* @param script the script
|
||||||
|
* @param args arguments
|
||||||
|
* @return result
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
Object execute(File script, Object[] args) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes scriptEngine closure
|
||||||
|
*
|
||||||
|
* @param closure closure
|
||||||
|
* @param args arguments
|
||||||
|
* @return result
|
||||||
|
*/
|
||||||
|
Object execute(Object closure, Object... args);
|
||||||
|
}
|
223
net-cli/src/main/java/org/jline/console/SystemRegistry.java
Normal file
223
net-cli/src/main/java/org/jline/console/SystemRegistry.java
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.jline.builtins.ConsoleOptionGetter;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregate command registries and dispatch command executions.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public interface SystemRegistry extends CommandRegistry, ConsoleOptionGetter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return systemRegistry of the current thread
|
||||||
|
*/
|
||||||
|
static SystemRegistry get() {
|
||||||
|
return Registeries.getInstance().getSystemRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add systemRegistry to the thread map
|
||||||
|
*
|
||||||
|
* @param systemRegistry the systemRegistry
|
||||||
|
*/
|
||||||
|
static void add(SystemRegistry systemRegistry) {
|
||||||
|
Registeries.getInstance().addRegistry(systemRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove systemRegistry of the current thread from the thread map
|
||||||
|
*/
|
||||||
|
static void remove() {
|
||||||
|
Registeries.getInstance().removeRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set command registries
|
||||||
|
*
|
||||||
|
* @param commandRegistries command registries used by the application
|
||||||
|
*/
|
||||||
|
void setCommandRegistries(CommandRegistry... commandRegistries);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register subcommand registry
|
||||||
|
*
|
||||||
|
* @param command main command
|
||||||
|
* @param subcommandRegistry subcommand registry
|
||||||
|
*/
|
||||||
|
void register(String command, CommandRegistry subcommandRegistry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize consoleEngine environment by executing console script
|
||||||
|
*
|
||||||
|
* @param script initialization script
|
||||||
|
*/
|
||||||
|
void initialize(File script);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return pipe names defined in systemRegistry
|
||||||
|
*/
|
||||||
|
Collection<String> getPipeNames();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns command completer that includes also console variable and script completion.
|
||||||
|
*
|
||||||
|
* @return command completer
|
||||||
|
*/
|
||||||
|
Completer completer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a command, method or syntax description for use in the JLine Widgets framework.
|
||||||
|
*
|
||||||
|
* @param line command line whose description to return
|
||||||
|
* @return command description for JLine TailTipWidgets to be displayed
|
||||||
|
* in the terminal status bar.
|
||||||
|
*/
|
||||||
|
CmdDesc commandDescription(CmdLine line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a command, script or evaluate scriptEngine statement
|
||||||
|
*
|
||||||
|
* @param line command line to be executed
|
||||||
|
* @return execution result
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
Object execute(String line) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete temporary console variables and reset output streams
|
||||||
|
*/
|
||||||
|
void cleanUp();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print exception on terminal
|
||||||
|
*
|
||||||
|
* @param exception exception to print on terminal
|
||||||
|
*/
|
||||||
|
void trace(Throwable exception);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print exception on terminal
|
||||||
|
*
|
||||||
|
* @param stack print stack trace if stack true otherwise message
|
||||||
|
* @param exception exception to be printed
|
||||||
|
*/
|
||||||
|
void trace(boolean stack, Throwable exception);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return console option value
|
||||||
|
*
|
||||||
|
* @param name the option name
|
||||||
|
* @return option value
|
||||||
|
*/
|
||||||
|
Object consoleOption(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return console option value
|
||||||
|
*
|
||||||
|
* @param name the option name
|
||||||
|
* @param defVal value to return if console option does not exists
|
||||||
|
* @return option value
|
||||||
|
*/
|
||||||
|
<T> T consoleOption(String name, T defVal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set console option value
|
||||||
|
*
|
||||||
|
* @param name the option name
|
||||||
|
* @param value value to assign console option
|
||||||
|
*/
|
||||||
|
void setConsoleOption(String name, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return terminal
|
||||||
|
*/
|
||||||
|
Terminal terminal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute command with arguments
|
||||||
|
*
|
||||||
|
* @param command command to be executed
|
||||||
|
* @param args arguments of the command
|
||||||
|
* @return command execution result
|
||||||
|
* @throws Exception in case of error
|
||||||
|
*/
|
||||||
|
Object invoke(String command, Object... args) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a line contains command/script that is known to this registry.
|
||||||
|
*
|
||||||
|
* @param line the parsed command line to test
|
||||||
|
* @return true if the specified line has a command registered
|
||||||
|
*/
|
||||||
|
boolean isCommandOrScript(ParsedLine line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether command is known to this registry.
|
||||||
|
*
|
||||||
|
* @param command the command to test
|
||||||
|
* @return true if the specified command is known
|
||||||
|
*/
|
||||||
|
boolean isCommandOrScript(String command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether alias is known command alias.
|
||||||
|
*
|
||||||
|
* @param alias the alias to test
|
||||||
|
* @return true if the alias is known command alias
|
||||||
|
*/
|
||||||
|
boolean isCommandAlias(String alias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orderly close SystemRegistry.
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage systemRegistry store
|
||||||
|
*/
|
||||||
|
class Registeries {
|
||||||
|
private static final Registeries instance = new Registeries();
|
||||||
|
private final Map<Long, SystemRegistry> systemRegisteries = new HashMap<>();
|
||||||
|
|
||||||
|
private Registeries() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Registeries getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Thread.getId() should be replaced with Thread.threadId() when minimum is JDK >= 19
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static long getThreadId() {
|
||||||
|
return Thread.currentThread().getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addRegistry(SystemRegistry systemRegistry) {
|
||||||
|
systemRegisteries.put(getThreadId(), systemRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SystemRegistry getSystemRegistry() {
|
||||||
|
return systemRegisteries.getOrDefault(getThreadId(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeRegistry() {
|
||||||
|
systemRegisteries.remove(getThreadId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,266 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.jline.console.CmdDesc;
|
||||||
|
import org.jline.console.CommandInput;
|
||||||
|
import org.jline.console.CommandMethods;
|
||||||
|
import org.jline.console.CommandRegistry;
|
||||||
|
import org.jline.reader.impl.completer.SystemCompleter;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
import org.jline.utils.AttributedStringBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CommandRegistry common methods.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCommandRegistry implements CommandRegistry {
|
||||||
|
private CmdRegistry cmdRegistry;
|
||||||
|
private Exception exception;
|
||||||
|
|
||||||
|
public AbstractCommandRegistry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CmdDesc doHelpDesc(String command, List<String> info, CmdDesc cmdDesc) {
|
||||||
|
List<AttributedString> mainDesc = new ArrayList<>();
|
||||||
|
AttributedStringBuilder asb = new AttributedStringBuilder();
|
||||||
|
asb.append(command.toLowerCase()).append(" - ");
|
||||||
|
for (String s : info) {
|
||||||
|
if (asb.length() == 0) {
|
||||||
|
asb.append("\t");
|
||||||
|
}
|
||||||
|
asb.append(s);
|
||||||
|
mainDesc.add(asb.toAttributedString());
|
||||||
|
asb = new AttributedStringBuilder();
|
||||||
|
asb.tabs(2);
|
||||||
|
}
|
||||||
|
asb = new AttributedStringBuilder();
|
||||||
|
asb.tabs(7);
|
||||||
|
asb.append("Usage:");
|
||||||
|
for (AttributedString as : cmdDesc.getMainDesc()) {
|
||||||
|
asb.append("\t");
|
||||||
|
asb.append(as);
|
||||||
|
mainDesc.add(asb.toAttributedString());
|
||||||
|
asb = new AttributedStringBuilder();
|
||||||
|
asb.tabs(7);
|
||||||
|
}
|
||||||
|
return new CmdDesc(mainDesc, new ArrayList<>(), cmdDesc.getOptsDesc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends Enum<T>> void registerCommands(
|
||||||
|
Map<T, String> commandName, Map<T, CommandMethods> commandExecute) {
|
||||||
|
cmdRegistry = new EnumCmdRegistry<>(commandName, commandExecute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerCommands(Map<String, CommandMethods> commandExecute) {
|
||||||
|
cmdRegistry = new NameCmdRegistry(commandExecute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(CommandSession session, String command, Object... args) throws Exception {
|
||||||
|
exception = null;
|
||||||
|
CommandMethods methods = getCommandMethods(command);
|
||||||
|
Object out = methods.execute().apply(new CommandInput(command, args, session));
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveException(Exception exception) {
|
||||||
|
this.exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasCommand(String command) {
|
||||||
|
return cmdRegistry.hasCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> commandNames() {
|
||||||
|
return cmdRegistry.commandNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> commandAliases() {
|
||||||
|
return cmdRegistry.commandAliases();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V extends Enum<V>> void rename(V command, String newName) {
|
||||||
|
cmdRegistry.rename(command, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void alias(String alias, String command) {
|
||||||
|
cmdRegistry.alias(alias, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SystemCompleter compileCompleters() {
|
||||||
|
return cmdRegistry.compileCompleters();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandMethods getCommandMethods(String command) {
|
||||||
|
return cmdRegistry.getCommandMethods(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object registeredCommand(String command) {
|
||||||
|
return cmdRegistry.command(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface CmdRegistry {
|
||||||
|
boolean hasCommand(String command);
|
||||||
|
|
||||||
|
Set<String> commandNames();
|
||||||
|
|
||||||
|
Map<String, String> commandAliases();
|
||||||
|
|
||||||
|
Object command(String command);
|
||||||
|
|
||||||
|
<V extends Enum<V>> void rename(V command, String newName);
|
||||||
|
|
||||||
|
void alias(String alias, String command);
|
||||||
|
|
||||||
|
SystemCompleter compileCompleters();
|
||||||
|
|
||||||
|
CommandMethods getCommandMethods(String command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EnumCmdRegistry<T extends Enum<T>> implements CmdRegistry {
|
||||||
|
private final Map<T, String> commandName;
|
||||||
|
private final Map<T, CommandMethods> commandExecute;
|
||||||
|
private final Map<String, String> aliasCommand = new HashMap<>();
|
||||||
|
private Map<String, T> nameCommand = new HashMap<>();
|
||||||
|
|
||||||
|
public EnumCmdRegistry(Map<T, String> commandName, Map<T, CommandMethods> commandExecute) {
|
||||||
|
this.commandName = commandName;
|
||||||
|
this.commandExecute = commandExecute;
|
||||||
|
doNameCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doNameCommand() {
|
||||||
|
nameCommand =
|
||||||
|
commandName.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> commandNames() {
|
||||||
|
return nameCommand.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> commandAliases() {
|
||||||
|
return aliasCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <V extends Enum<V>> void rename(V command, String newName) {
|
||||||
|
if (nameCommand.containsKey(newName)) {
|
||||||
|
throw new IllegalArgumentException("Duplicate command name!");
|
||||||
|
} else if (!commandName.containsKey(command)) {
|
||||||
|
throw new IllegalArgumentException("Command does not exists!");
|
||||||
|
}
|
||||||
|
commandName.put((T) command, newName);
|
||||||
|
doNameCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void alias(String alias, String command) {
|
||||||
|
if (!nameCommand.containsKey(command)) {
|
||||||
|
throw new IllegalArgumentException("Command does not exists!");
|
||||||
|
}
|
||||||
|
aliasCommand.put(alias, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasCommand(String name) {
|
||||||
|
return nameCommand.containsKey(name) || aliasCommand.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemCompleter compileCompleters() {
|
||||||
|
SystemCompleter out = new SystemCompleter();
|
||||||
|
for (Map.Entry<T, String> entry : commandName.entrySet()) {
|
||||||
|
out.add(
|
||||||
|
entry.getValue(),
|
||||||
|
commandExecute.get(entry.getKey()).compileCompleter().apply(entry.getValue()));
|
||||||
|
}
|
||||||
|
out.addAliases(aliasCommand);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T command(String name) {
|
||||||
|
T out;
|
||||||
|
name = aliasCommand.getOrDefault(name, name);
|
||||||
|
if (nameCommand.containsKey(name)) {
|
||||||
|
out = nameCommand.get(name);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Command does not exists!");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandMethods getCommandMethods(String command) {
|
||||||
|
return commandExecute.get(command(command));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NameCmdRegistry implements CmdRegistry {
|
||||||
|
private final Map<String, CommandMethods> commandExecute;
|
||||||
|
private final Map<String, String> aliasCommand = new HashMap<>();
|
||||||
|
|
||||||
|
public NameCmdRegistry(Map<String, CommandMethods> commandExecute) {
|
||||||
|
this.commandExecute = commandExecute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> commandNames() {
|
||||||
|
return commandExecute.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> commandAliases() {
|
||||||
|
return aliasCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V extends Enum<V>> void rename(V command, String newName) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void alias(String alias, String command) {
|
||||||
|
if (!commandExecute.containsKey(command)) {
|
||||||
|
throw new IllegalArgumentException("Command does not exists!");
|
||||||
|
}
|
||||||
|
aliasCommand.put(alias, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasCommand(String name) {
|
||||||
|
return commandExecute.containsKey(name) || aliasCommand.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemCompleter compileCompleters() {
|
||||||
|
SystemCompleter out = new SystemCompleter();
|
||||||
|
for (String c : commandExecute.keySet()) {
|
||||||
|
out.add(c, commandExecute.get(c).compileCompleter().apply(c));
|
||||||
|
}
|
||||||
|
out.addAliases(aliasCommand);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String command(String name) {
|
||||||
|
if (commandExecute.containsKey(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return aliasCommand.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandMethods getCommandMethods(String command) {
|
||||||
|
return commandExecute.get(command(command));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
287
net-cli/src/main/java/org/jline/console/impl/Builtins.java
Normal file
287
net-cli/src/main/java/org/jline/console/impl/Builtins.java
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2022, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console.impl;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import org.jline.builtins.Commands;
|
||||||
|
import org.jline.builtins.Completers.FilesCompleter;
|
||||||
|
import org.jline.builtins.Completers.OptDesc;
|
||||||
|
import org.jline.builtins.Completers.OptionCompleter;
|
||||||
|
import org.jline.builtins.ConfigurationPath;
|
||||||
|
import org.jline.builtins.TTop;
|
||||||
|
import org.jline.console.CommandInput;
|
||||||
|
import org.jline.console.CommandMethods;
|
||||||
|
import org.jline.console.CommandRegistry;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.LineReader.Option;
|
||||||
|
import org.jline.reader.Widget;
|
||||||
|
import org.jline.reader.impl.completer.ArgumentCompleter;
|
||||||
|
import org.jline.reader.impl.completer.NullCompleter;
|
||||||
|
import org.jline.reader.impl.completer.StringsCompleter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builtins: create tab completers, execute and create descriptions for builtins commands.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public class Builtins extends JlineCommandRegistry implements CommandRegistry {
|
||||||
|
private final ConfigurationPath configPath;
|
||||||
|
private final Function<String, Widget> widgetCreator;
|
||||||
|
private final Supplier<Path> workDir;
|
||||||
|
private LineReader reader;
|
||||||
|
public Builtins(Path workDir, ConfigurationPath configPath, Function<String, Widget> widgetCreator) {
|
||||||
|
this(null, () -> workDir, configPath, widgetCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builtins(
|
||||||
|
Set<Command> commands, Path workDir, ConfigurationPath configpath, Function<String, Widget> widgetCreator) {
|
||||||
|
this(commands, () -> workDir, configpath, widgetCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builtins(Supplier<Path> workDir, ConfigurationPath configPath, Function<String, Widget> widgetCreator) {
|
||||||
|
this(null, workDir, configPath, widgetCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public Builtins(
|
||||||
|
Set<Command> commands,
|
||||||
|
Supplier<Path> workDir,
|
||||||
|
ConfigurationPath configpath,
|
||||||
|
Function<String, Widget> widgetCreator) {
|
||||||
|
super();
|
||||||
|
Objects.requireNonNull(configpath);
|
||||||
|
this.configPath = configpath;
|
||||||
|
this.widgetCreator = widgetCreator;
|
||||||
|
this.workDir = workDir;
|
||||||
|
Set<Command> cmds;
|
||||||
|
Map<Command, String> commandName = new HashMap<>();
|
||||||
|
Map<Command, CommandMethods> commandExecute = new HashMap<>();
|
||||||
|
if (commands == null) {
|
||||||
|
cmds = new HashSet<>(EnumSet.allOf(Command.class));
|
||||||
|
} else {
|
||||||
|
cmds = new HashSet<>(commands);
|
||||||
|
}
|
||||||
|
for (Command c : cmds) {
|
||||||
|
commandName.put(c, c.name().toLowerCase());
|
||||||
|
}
|
||||||
|
commandExecute.put(Command.LESS, new CommandMethods(this::less, this::lessCompleter));
|
||||||
|
commandExecute.put(Command.HISTORY, new CommandMethods(this::history, this::historyCompleter));
|
||||||
|
commandExecute.put(Command.WIDGET, new CommandMethods(this::widget, this::widgetCompleter));
|
||||||
|
commandExecute.put(Command.KEYMAP, new CommandMethods(this::keymap, this::defaultCompleter));
|
||||||
|
commandExecute.put(Command.SETOPT, new CommandMethods(this::setopt, this::setoptCompleter));
|
||||||
|
commandExecute.put(Command.SETVAR, new CommandMethods(this::setvar, this::setvarCompleter));
|
||||||
|
commandExecute.put(Command.UNSETOPT, new CommandMethods(this::unsetopt, this::unsetoptCompleter));
|
||||||
|
commandExecute.put(Command.TTOP, new CommandMethods(this::ttop, this::defaultCompleter));
|
||||||
|
commandExecute.put(Command.COLORS, new CommandMethods(this::colors, this::defaultCompleter));
|
||||||
|
registerCommands(commandName, commandExecute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLineReader(LineReader reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void less(CommandInput input) {
|
||||||
|
try {
|
||||||
|
Commands.less(
|
||||||
|
input.terminal(), input.in(), input.out(), input.err(), workDir.get(), input.xargs(), configPath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
saveException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void history(CommandInput input) {
|
||||||
|
try {
|
||||||
|
Commands.history(reader, input.out(), input.err(), workDir.get(), input.args());
|
||||||
|
} catch (Exception e) {
|
||||||
|
saveException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void widget(CommandInput input) {
|
||||||
|
try {
|
||||||
|
Commands.widget(reader, input.out(), input.err(), widgetCreator, input.args());
|
||||||
|
} catch (Exception e) {
|
||||||
|
saveException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void keymap(CommandInput input) {
|
||||||
|
try {
|
||||||
|
Commands.keymap(reader, input.out(), input.err(), input.args());
|
||||||
|
} catch (Exception e) {
|
||||||
|
saveException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setopt(CommandInput input) {
|
||||||
|
try {
|
||||||
|
Commands.setopt(reader, input.out(), input.err(), input.args());
|
||||||
|
} catch (Exception e) {
|
||||||
|
saveException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setvar(CommandInput input) {
|
||||||
|
try {
|
||||||
|
Commands.setvar(reader, input.out(), input.err(), input.args());
|
||||||
|
} catch (Exception e) {
|
||||||
|
saveException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unsetopt(CommandInput input) {
|
||||||
|
try {
|
||||||
|
Commands.unsetopt(reader, input.out(), input.err(), input.args());
|
||||||
|
} catch (Exception e) {
|
||||||
|
saveException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ttop(CommandInput input) {
|
||||||
|
try {
|
||||||
|
TTop.ttop(input.terminal(), input.out(), input.err(), input.args());
|
||||||
|
} catch (Exception e) {
|
||||||
|
saveException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void colors(CommandInput input) {
|
||||||
|
try {
|
||||||
|
Commands.colors(input.terminal(), input.out(), input.args());
|
||||||
|
} catch (Exception e) {
|
||||||
|
saveException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> unsetOptions(boolean set) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
for (Option option : Option.values()) {
|
||||||
|
if (set == (reader.isSet(option) == option.isDef())) {
|
||||||
|
out.add((option.isDef() ? "no-" : "")
|
||||||
|
+ option.toString().toLowerCase().replace('_', '-'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Completer> highlighterCompleter(String name) {
|
||||||
|
List<Completer> completers = new ArrayList<>();
|
||||||
|
List<OptDesc> optDescs = commandOptions(name);
|
||||||
|
completers.add(new ArgumentCompleter(
|
||||||
|
NullCompleter.INSTANCE, new OptionCompleter(NullCompleter.INSTANCE, optDescs, 1)));
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> allWidgets() {
|
||||||
|
Set<String> out = new HashSet<>();
|
||||||
|
for (String s : reader.getWidgets().keySet()) {
|
||||||
|
out.add(s);
|
||||||
|
out.add(reader.getWidgets().get(s).toString());
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Completer> lessCompleter(String name) {
|
||||||
|
List<Completer> completers = new ArrayList<>();
|
||||||
|
completers.add(new ArgumentCompleter(
|
||||||
|
NullCompleter.INSTANCE, new OptionCompleter(new FilesCompleter(workDir), this::commandOptions, 1)));
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Completer> historyCompleter(String name) {
|
||||||
|
List<Completer> completers = new ArrayList<>();
|
||||||
|
List<OptDesc> optDescs = commandOptions(name);
|
||||||
|
for (OptDesc o : optDescs) {
|
||||||
|
if (o.shortOption() != null
|
||||||
|
&& (o.shortOption().equals("-A")
|
||||||
|
|| o.shortOption().equals("-W")
|
||||||
|
|| o.shortOption().equals("-R"))) {
|
||||||
|
o.setValueCompleter(new FilesCompleter(workDir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completers.add(new ArgumentCompleter(
|
||||||
|
NullCompleter.INSTANCE, new OptionCompleter(NullCompleter.INSTANCE, optDescs, 1)));
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Completer> widgetCompleter(String name) {
|
||||||
|
List<Completer> completers = new ArrayList<>();
|
||||||
|
List<OptDesc> optDescs = commandOptions(name);
|
||||||
|
Candidate aliasOption = new Candidate("-A", "-A", null, null, null, null, true);
|
||||||
|
Iterator<OptDesc> i = optDescs.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
OptDesc o = i.next();
|
||||||
|
if (o.shortOption() != null) {
|
||||||
|
if (o.shortOption().equals("-D")) {
|
||||||
|
o.setValueCompleter(
|
||||||
|
new StringsCompleter(() -> reader.getWidgets().keySet()));
|
||||||
|
} else if (o.shortOption().equals("-A")) {
|
||||||
|
aliasOption =
|
||||||
|
new Candidate(o.shortOption(), o.shortOption(), null, o.description(), null, null, true);
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completers.add(new ArgumentCompleter(
|
||||||
|
NullCompleter.INSTANCE, new OptionCompleter(NullCompleter.INSTANCE, optDescs, 1)));
|
||||||
|
completers.add(new ArgumentCompleter(
|
||||||
|
NullCompleter.INSTANCE,
|
||||||
|
new StringsCompleter(aliasOption),
|
||||||
|
new StringsCompleter(this::allWidgets),
|
||||||
|
new StringsCompleter(() -> reader.getWidgets().keySet()),
|
||||||
|
NullCompleter.INSTANCE));
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Completer> setvarCompleter(String name) {
|
||||||
|
List<Completer> completers = new ArrayList<>();
|
||||||
|
completers.add(new ArgumentCompleter(
|
||||||
|
NullCompleter.INSTANCE,
|
||||||
|
new StringsCompleter(() -> reader.getVariables().keySet()),
|
||||||
|
NullCompleter.INSTANCE));
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Completer> setoptCompleter(String name) {
|
||||||
|
List<Completer> completers = new ArrayList<>();
|
||||||
|
completers.add(new ArgumentCompleter(NullCompleter.INSTANCE, new StringsCompleter(() -> unsetOptions(true))));
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Completer> unsetoptCompleter(String name) {
|
||||||
|
List<Completer> completers = new ArrayList<>();
|
||||||
|
completers.add(new ArgumentCompleter(NullCompleter.INSTANCE, new StringsCompleter(() -> unsetOptions(false))));
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Command {
|
||||||
|
LESS,
|
||||||
|
HISTORY,
|
||||||
|
WIDGET,
|
||||||
|
KEYMAP,
|
||||||
|
SETOPT,
|
||||||
|
SETVAR,
|
||||||
|
UNSETOPT,
|
||||||
|
TTOP,
|
||||||
|
COLORS
|
||||||
|
}
|
||||||
|
}
|
1389
net-cli/src/main/java/org/jline/console/impl/ConsoleEngineImpl.java
Normal file
1389
net-cli/src/main/java/org/jline/console/impl/ConsoleEngineImpl.java
Normal file
File diff suppressed because it is too large
Load diff
1239
net-cli/src/main/java/org/jline/console/impl/DefaultPrinter.java
Normal file
1239
net-cli/src/main/java/org/jline/console/impl/DefaultPrinter.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,222 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import org.jline.builtins.Completers.AnyCompleter;
|
||||||
|
import org.jline.builtins.Completers.OptDesc;
|
||||||
|
import org.jline.builtins.Completers.OptionCompleter;
|
||||||
|
import org.jline.builtins.Options;
|
||||||
|
import org.jline.builtins.Options.HelpException;
|
||||||
|
import org.jline.console.ArgDesc;
|
||||||
|
import org.jline.console.CmdDesc;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.impl.completer.ArgumentCompleter;
|
||||||
|
import org.jline.reader.impl.completer.NullCompleter;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CommandRegistry common methods for JLine commands that are using HelpException.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public abstract class JlineCommandRegistry extends AbstractCommandRegistry {
|
||||||
|
|
||||||
|
public JlineCommandRegistry() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Utils for helpMessage parsing
|
||||||
|
//
|
||||||
|
private static AttributedString highlightComment(String comment) {
|
||||||
|
return HelpException.highlightComment(comment, HelpException.defaultStyle());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] helpLines(String helpMessage, boolean body) {
|
||||||
|
return new HelpLines(helpMessage, body).lines();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CmdDesc compileCommandDescription(String helpMessage) {
|
||||||
|
List<AttributedString> main = new ArrayList<>();
|
||||||
|
Map<String, List<AttributedString>> options = new HashMap<>();
|
||||||
|
String prevOpt = null;
|
||||||
|
boolean mainDone = false;
|
||||||
|
HelpLines hl = new HelpLines(helpMessage, true);
|
||||||
|
for (String s : hl.lines()) {
|
||||||
|
if (s.matches("^\\s+-.*$")) {
|
||||||
|
mainDone = true;
|
||||||
|
int ind = s.lastIndexOf(" ");
|
||||||
|
if (ind > 0) {
|
||||||
|
String o = s.substring(0, ind);
|
||||||
|
String d = s.substring(ind);
|
||||||
|
if (o.trim().length() > 0) {
|
||||||
|
prevOpt = o.trim();
|
||||||
|
options.put(prevOpt, new ArrayList<>(Collections.singletonList(highlightComment(d.trim()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (s.matches("^[\\s]{20}.*$") && prevOpt != null && options.containsKey(prevOpt)) {
|
||||||
|
int ind = s.lastIndexOf(" ");
|
||||||
|
if (ind > 0) {
|
||||||
|
options.get(prevOpt).add(highlightComment(s.substring(ind).trim()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prevOpt = null;
|
||||||
|
}
|
||||||
|
if (!mainDone) {
|
||||||
|
main.add(HelpException.highlightSyntax(s.trim(), HelpException.defaultStyle(), hl.subcommands()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new CmdDesc(main, ArgDesc.doArgNames(Collections.singletonList("")), options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<OptDesc> compileCommandOptions(String helpMessage) {
|
||||||
|
List<OptDesc> out = new ArrayList<>();
|
||||||
|
for (String s : helpLines(helpMessage, true)) {
|
||||||
|
if (s.matches("^\\s+-.*$")) {
|
||||||
|
int ind = s.lastIndexOf(" ");
|
||||||
|
if (ind > 0) {
|
||||||
|
String[] op = s.substring(0, ind).trim().split("\\s+");
|
||||||
|
String d = s.substring(ind).trim();
|
||||||
|
String so = null;
|
||||||
|
String lo = null;
|
||||||
|
if (op.length == 1) {
|
||||||
|
if (op[0].startsWith("--")) {
|
||||||
|
lo = op[0];
|
||||||
|
} else {
|
||||||
|
so = op[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
so = op[0];
|
||||||
|
lo = op[1];
|
||||||
|
}
|
||||||
|
boolean hasValue = false;
|
||||||
|
if (lo != null && lo.contains("=")) {
|
||||||
|
hasValue = true;
|
||||||
|
lo = lo.split("=")[0];
|
||||||
|
}
|
||||||
|
out.add(new OptDesc(so, lo, d, hasValue ? AnyCompleter.INSTANCE : null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> compileCommandInfo(String helpMessage) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
boolean first = true;
|
||||||
|
for (String s : helpLines(helpMessage, false)) {
|
||||||
|
if (first && s.contains(" - ")) {
|
||||||
|
out.add(s.substring(s.indexOf(" - ") + 3).trim());
|
||||||
|
} else {
|
||||||
|
out.add(s.trim());
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> commandInfo(String command) {
|
||||||
|
try {
|
||||||
|
Object[] args = {"--help"};
|
||||||
|
if (command.equals("help")) {
|
||||||
|
args = new Object[]{};
|
||||||
|
}
|
||||||
|
invoke(new CommandSession(), command, args);
|
||||||
|
} catch (HelpException e) {
|
||||||
|
return compileCommandInfo(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.info("Error while getting command info", e);
|
||||||
|
if (Log.isDebugEnabled()) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("JlineCommandRegistry.commandInfo() method must be overridden in class "
|
||||||
|
+ this.getClass().getCanonicalName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CmdDesc commandDescription(List<String> args) {
|
||||||
|
String command = args != null && !args.isEmpty() ? args.get(0) : "";
|
||||||
|
try {
|
||||||
|
invoke(new CommandSession(), command, "--help");
|
||||||
|
} catch (HelpException e) {
|
||||||
|
return compileCommandDescription(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"JlineCommandRegistry.commandDescription() method must be overridden in class "
|
||||||
|
+ this.getClass().getCanonicalName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OptDesc> commandOptions(String command) {
|
||||||
|
try {
|
||||||
|
invoke(new CommandSession(), command, "--help");
|
||||||
|
} catch (HelpException e) {
|
||||||
|
return compileCommandOptions(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Completer> defaultCompleter(String command) {
|
||||||
|
List<Completer> completers = new ArrayList<>();
|
||||||
|
completers.add(new ArgumentCompleter(
|
||||||
|
NullCompleter.INSTANCE, new OptionCompleter(NullCompleter.INSTANCE, this::commandOptions, 1)));
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options parseOptions(String[] usage, Object[] args) throws HelpException {
|
||||||
|
Options opt = Options.compile(usage).parse(args);
|
||||||
|
if (opt.isSet("help")) {
|
||||||
|
throw new HelpException(opt.usage());
|
||||||
|
}
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HelpLines {
|
||||||
|
private final String helpMessage;
|
||||||
|
private final boolean body;
|
||||||
|
private boolean subcommands;
|
||||||
|
|
||||||
|
public HelpLines(String helpMessage, boolean body) {
|
||||||
|
this.helpMessage = helpMessage;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] lines() {
|
||||||
|
String out = "";
|
||||||
|
Matcher tm = Pattern.compile("(^|\\n)(Usage|Summary)(:)").matcher(helpMessage);
|
||||||
|
if (tm.find()) {
|
||||||
|
subcommands = tm.group(2).matches("Summary");
|
||||||
|
if (body) {
|
||||||
|
out = helpMessage.substring(tm.end(3));
|
||||||
|
} else {
|
||||||
|
out = helpMessage.substring(0, tm.start(1));
|
||||||
|
}
|
||||||
|
} else if (!body) {
|
||||||
|
out = helpMessage;
|
||||||
|
}
|
||||||
|
return out.split("\\r?\\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean subcommands() {
|
||||||
|
return subcommands;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,397 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2022, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.console.impl;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import org.jline.builtins.Styles;
|
||||||
|
import org.jline.builtins.SyntaxHighlighter;
|
||||||
|
import org.jline.console.SystemRegistry;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
import org.jline.reader.Parser;
|
||||||
|
import org.jline.reader.impl.DefaultHighlighter;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
import org.jline.utils.AttributedStringBuilder;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
import org.jline.utils.OSUtils;
|
||||||
|
import org.jline.utils.StyleResolver;
|
||||||
|
import static org.jline.builtins.Styles.NANORC_THEME;
|
||||||
|
import static org.jline.builtins.SyntaxHighlighter.REGEX_TOKEN_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight command and language syntax using nanorc highlighter.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public class SystemHighlighter extends DefaultHighlighter {
|
||||||
|
private static final String REGEX_COMMENT_LINE = "\\s*#.*";
|
||||||
|
private static final String READER_COLORS = "READER_COLORS";
|
||||||
|
protected final SyntaxHighlighter commandHighlighter;
|
||||||
|
protected final SyntaxHighlighter argsHighlighter;
|
||||||
|
protected final SyntaxHighlighter langHighlighter;
|
||||||
|
protected final SystemRegistry systemRegistry;
|
||||||
|
protected final Map<String, FileHighlightCommand> fileHighlight = new HashMap<>();
|
||||||
|
protected final Map<String, SyntaxHighlighter> specificHighlighter = new HashMap<>();
|
||||||
|
private final List<Supplier<Boolean>> externalHighlightersRefresh = new ArrayList<>();
|
||||||
|
protected int commandIndex;
|
||||||
|
private StyleResolver resolver = Styles.lsStyle();
|
||||||
|
|
||||||
|
public SystemHighlighter(
|
||||||
|
SyntaxHighlighter commandHighlighter,
|
||||||
|
SyntaxHighlighter argsHighlighter,
|
||||||
|
SyntaxHighlighter langHighlighter) {
|
||||||
|
this.commandHighlighter = commandHighlighter;
|
||||||
|
this.argsHighlighter = argsHighlighter;
|
||||||
|
this.langHighlighter = langHighlighter;
|
||||||
|
this.systemRegistry = SystemRegistry.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpecificHighlighter(String command, SyntaxHighlighter highlighter) {
|
||||||
|
this.specificHighlighter.put(command, highlighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh(LineReader lineReader) {
|
||||||
|
Path currentTheme = null;
|
||||||
|
if (commandHighlighter != null) {
|
||||||
|
commandHighlighter.refresh();
|
||||||
|
currentTheme = compareThemes(commandHighlighter, currentTheme);
|
||||||
|
}
|
||||||
|
if (argsHighlighter != null) {
|
||||||
|
argsHighlighter.refresh();
|
||||||
|
currentTheme = compareThemes(argsHighlighter, currentTheme);
|
||||||
|
}
|
||||||
|
if (langHighlighter != null) {
|
||||||
|
langHighlighter.refresh();
|
||||||
|
currentTheme = compareThemes(langHighlighter, currentTheme);
|
||||||
|
}
|
||||||
|
for (SyntaxHighlighter sh : specificHighlighter.values()) {
|
||||||
|
sh.refresh();
|
||||||
|
currentTheme = compareThemes(sh, currentTheme);
|
||||||
|
}
|
||||||
|
if (currentTheme != null) {
|
||||||
|
try (BufferedReader reader = new BufferedReader(new FileReader(currentTheme.toFile()))) {
|
||||||
|
String line;
|
||||||
|
Map<String, String> tokens = new HashMap<>();
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
String[] parts = line.trim().split("\\s+", 2);
|
||||||
|
if (parts[0].matches(REGEX_TOKEN_NAME) && parts.length == 2) {
|
||||||
|
tokens.put(parts[0], parts[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SystemRegistry registry = SystemRegistry.get();
|
||||||
|
registry.setConsoleOption(NANORC_THEME, tokens);
|
||||||
|
Map<String, String> readerColors = registry.consoleOption(READER_COLORS, new HashMap<>());
|
||||||
|
Styles.StyleCompiler styleCompiler = new Styles.StyleCompiler(readerColors);
|
||||||
|
for (String key : readerColors.keySet()) {
|
||||||
|
lineReader.setVariable(key, styleCompiler.getStyle(key));
|
||||||
|
}
|
||||||
|
for (Supplier<Boolean> refresh : externalHighlightersRefresh) {
|
||||||
|
refresh.get();
|
||||||
|
}
|
||||||
|
resolver = Styles.lsStyle();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.warn(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addExternalHighlighterRefresh(Supplier<Boolean> refresh) {
|
||||||
|
externalHighlightersRefresh.add(refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path compareThemes(SyntaxHighlighter highlighter, Path currentTheme) {
|
||||||
|
Path out;
|
||||||
|
if (currentTheme != null) {
|
||||||
|
Path theme = highlighter.getCurrentTheme();
|
||||||
|
try {
|
||||||
|
if (theme != null && !Files.isSameFile(theme, currentTheme)) {
|
||||||
|
Log.warn("Multiple nanorc themes are in use!");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.warn(e.getMessage());
|
||||||
|
}
|
||||||
|
out = currentTheme;
|
||||||
|
} else {
|
||||||
|
out = highlighter.getCurrentTheme();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AttributedString highlight(LineReader reader, String buffer) {
|
||||||
|
return doDefaultHighlight(reader) ? super.highlight(reader, buffer) : systemHighlight(reader, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFileHighlight(String... commands) {
|
||||||
|
for (String c : commands) {
|
||||||
|
fileHighlight.put(c, new FileHighlightCommand());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFileHighlight(String command, String subcommand, Collection<String> fileOptions) {
|
||||||
|
fileHighlight.put(command, new FileHighlightCommand(subcommand, fileOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doDefaultHighlight(LineReader reader) {
|
||||||
|
String search = reader.getSearchTerm();
|
||||||
|
return ((search != null && search.length() > 0)
|
||||||
|
|| reader.getRegionActive() != LineReader.RegionType.NONE
|
||||||
|
|| errorIndex > -1
|
||||||
|
|| errorPattern != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AttributedString systemHighlight(LineReader reader, String buffer) {
|
||||||
|
AttributedString out;
|
||||||
|
Parser parser = reader.getParser();
|
||||||
|
ParsedLine pl = parser.parse(buffer, 0, Parser.ParseContext.SPLIT_LINE);
|
||||||
|
String command = pl.words().size() > 0 ? parser.getCommand(pl.words().get(0)) : "";
|
||||||
|
command = command.startsWith("!") ? "!" : command;
|
||||||
|
commandIndex = buffer.indexOf(command) + command.length();
|
||||||
|
if (buffer.trim().isEmpty()) {
|
||||||
|
out = new AttributedStringBuilder().append(buffer).toAttributedString();
|
||||||
|
} else if (specificHighlighter.containsKey(command)) {
|
||||||
|
AttributedStringBuilder asb = new AttributedStringBuilder();
|
||||||
|
if (commandHighlighter == null) {
|
||||||
|
asb.append(specificHighlighter.get(command).reset().highlight(buffer));
|
||||||
|
} else {
|
||||||
|
highlightCommand(buffer.substring(0, commandIndex), asb);
|
||||||
|
asb.append(specificHighlighter.get(command).reset().highlight(buffer.substring(commandIndex)));
|
||||||
|
}
|
||||||
|
out = asb.toAttributedString();
|
||||||
|
} else if (fileHighlight.containsKey(command)) {
|
||||||
|
FileHighlightCommand fhc = fileHighlight.get(command);
|
||||||
|
if (!fhc.hasFileOptions()) {
|
||||||
|
out = doFileArgsHighlight(reader, buffer, pl.words(), fhc);
|
||||||
|
} else {
|
||||||
|
out = doFileOptsHighlight(reader, buffer, pl.words(), fhc);
|
||||||
|
}
|
||||||
|
} else if (systemRegistry.isCommandOrScript(command)
|
||||||
|
|| systemRegistry.isCommandAlias(command)
|
||||||
|
|| command.isEmpty()
|
||||||
|
|| buffer.matches(REGEX_COMMENT_LINE)) {
|
||||||
|
out = doCommandHighlight(buffer);
|
||||||
|
} else if (langHighlighter != null) {
|
||||||
|
out = langHighlighter.reset().highlight(buffer);
|
||||||
|
} else {
|
||||||
|
out = new AttributedStringBuilder().append(buffer).toAttributedString();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AttributedString doFileOptsHighlight(
|
||||||
|
LineReader reader, String buffer, List<String> words, FileHighlightCommand fhc) {
|
||||||
|
AttributedStringBuilder asb = new AttributedStringBuilder();
|
||||||
|
if (commandIndex < 0) {
|
||||||
|
highlightCommand(buffer, asb);
|
||||||
|
} else {
|
||||||
|
highlightCommand(buffer.substring(0, commandIndex), asb);
|
||||||
|
if (!fhc.isSubcommand() || (words.size() > 2 && fhc.getSubcommand().equals(words.get(1)))) {
|
||||||
|
boolean subCommand = fhc.isSubcommand();
|
||||||
|
int idx = buffer.indexOf(words.get(0)) + words.get(0).length();
|
||||||
|
boolean fileOption = false;
|
||||||
|
for (int i = 1; i < words.size(); i++) {
|
||||||
|
int nextIdx = buffer.substring(idx).indexOf(words.get(i)) + idx;
|
||||||
|
for (int j = idx; j < nextIdx; j++) {
|
||||||
|
asb.append(buffer.charAt(j));
|
||||||
|
}
|
||||||
|
String word = words.get(i);
|
||||||
|
if (subCommand) {
|
||||||
|
subCommand = false;
|
||||||
|
highlightArgs(word, asb);
|
||||||
|
} else if (word.contains("=")
|
||||||
|
&& fhc.getFileOptions().contains(word.substring(0, word.indexOf("=")))) {
|
||||||
|
highlightArgs(word.substring(0, word.indexOf("=") + 1), asb);
|
||||||
|
highlightFileArg(reader, word.substring(word.indexOf("=") + 1), asb);
|
||||||
|
} else if (fhc.getFileOptions().contains(word)) {
|
||||||
|
highlightArgs(word, asb);
|
||||||
|
fileOption = true;
|
||||||
|
} else if (fileOption) {
|
||||||
|
highlightFileArg(reader, word, asb);
|
||||||
|
} else {
|
||||||
|
highlightArgs(word, asb);
|
||||||
|
fileOption = false;
|
||||||
|
}
|
||||||
|
idx = nextIdx + word.length();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
highlightArgs(buffer.substring(commandIndex), asb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return asb.toAttributedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AttributedString doFileArgsHighlight(
|
||||||
|
LineReader reader, String buffer, List<String> words, FileHighlightCommand fhc) {
|
||||||
|
AttributedStringBuilder asb = new AttributedStringBuilder();
|
||||||
|
if (commandIndex < 0) {
|
||||||
|
highlightCommand(buffer, asb);
|
||||||
|
} else {
|
||||||
|
highlightCommand(buffer.substring(0, commandIndex), asb);
|
||||||
|
if (!fhc.isSubcommand() || (words.size() > 2 && fhc.getSubcommand().equals(words.get(1)))) {
|
||||||
|
boolean subCommand = fhc.isSubcommand();
|
||||||
|
int idx = buffer.indexOf(words.get(0)) + words.get(0).length();
|
||||||
|
for (int i = 1; i < words.size(); i++) {
|
||||||
|
int nextIdx = buffer.substring(idx).indexOf(words.get(i)) + idx;
|
||||||
|
for (int j = idx; j < nextIdx; j++) {
|
||||||
|
asb.append(buffer.charAt(j));
|
||||||
|
}
|
||||||
|
if (subCommand) {
|
||||||
|
subCommand = false;
|
||||||
|
highlightArgs(words.get(i), asb);
|
||||||
|
} else {
|
||||||
|
highlightFileArg(reader, words.get(i), asb);
|
||||||
|
idx = nextIdx + words.get(i).length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
highlightArgs(buffer.substring(commandIndex), asb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return asb.toAttributedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AttributedString doCommandHighlight(String buffer) {
|
||||||
|
AttributedString out;
|
||||||
|
if (commandHighlighter != null || argsHighlighter != null) {
|
||||||
|
AttributedStringBuilder asb = new AttributedStringBuilder();
|
||||||
|
if (commandIndex < 0 || buffer.matches(REGEX_COMMENT_LINE)) {
|
||||||
|
highlightCommand(buffer, asb);
|
||||||
|
} else {
|
||||||
|
highlightCommand(buffer.substring(0, commandIndex), asb);
|
||||||
|
highlightArgs(buffer.substring(commandIndex), asb);
|
||||||
|
}
|
||||||
|
out = asb.toAttributedString();
|
||||||
|
} else {
|
||||||
|
out = new AttributedStringBuilder().append(buffer).toAttributedString();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void highlightFileArg(LineReader reader, String arg, AttributedStringBuilder asb) {
|
||||||
|
if (arg.startsWith("-")) {
|
||||||
|
highlightArgs(arg, asb);
|
||||||
|
} else {
|
||||||
|
String separator = reader.isSet(LineReader.Option.USE_FORWARD_SLASH)
|
||||||
|
? "/"
|
||||||
|
: Paths.get(System.getProperty("user.dir")).getFileSystem().getSeparator();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try {
|
||||||
|
Path path = new File(arg).toPath();
|
||||||
|
Iterator<Path> iterator = path.iterator();
|
||||||
|
if (OSUtils.IS_WINDOWS && arg.matches("^[A-Za-z]:.*$")) {
|
||||||
|
if (arg.length() == 2) {
|
||||||
|
sb.append(arg);
|
||||||
|
asb.append(arg);
|
||||||
|
} else if (arg.charAt(2) == separator.charAt(0)) {
|
||||||
|
sb.append(arg, 0, 3);
|
||||||
|
asb.append(arg.substring(0, 3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arg.startsWith(separator)) {
|
||||||
|
sb.append(separator);
|
||||||
|
asb.append(separator);
|
||||||
|
}
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
sb.append(iterator.next());
|
||||||
|
highlightFile(new File(sb.toString()).toPath(), asb);
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
sb.append(separator);
|
||||||
|
asb.append(separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arg.length() > 2 && !arg.matches("^[A-Za-z]:" + separator) && arg.endsWith(separator)) {
|
||||||
|
asb.append(separator);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
asb.append(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void highlightFile(Path path, AttributedStringBuilder asb) {
|
||||||
|
AttributedStringBuilder sb = new AttributedStringBuilder();
|
||||||
|
String name = path.getFileName().toString();
|
||||||
|
int idx = name.lastIndexOf(".");
|
||||||
|
String type = idx != -1 ? ".*" + name.substring(idx) : null;
|
||||||
|
if (Files.isSymbolicLink(path)) {
|
||||||
|
sb.styled(resolver.resolve(".ln"), name);
|
||||||
|
} else if (Files.isDirectory(path)) {
|
||||||
|
sb.styled(resolver.resolve(".di"), name);
|
||||||
|
} else if (Files.isExecutable(path) && !OSUtils.IS_WINDOWS) {
|
||||||
|
sb.styled(resolver.resolve(".ex"), name);
|
||||||
|
} else if (type != null && resolver.resolve(type).getStyle() != 0) {
|
||||||
|
sb.styled(resolver.resolve(type), name);
|
||||||
|
} else if (Files.isRegularFile(path)) {
|
||||||
|
sb.styled(resolver.resolve(".fi"), name);
|
||||||
|
} else {
|
||||||
|
sb.append(name);
|
||||||
|
}
|
||||||
|
asb.append(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void highlightArgs(String args, AttributedStringBuilder asb) {
|
||||||
|
if (argsHighlighter != null) {
|
||||||
|
asb.append(argsHighlighter.reset().highlight(args));
|
||||||
|
} else {
|
||||||
|
asb.append(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void highlightCommand(String command, AttributedStringBuilder asb) {
|
||||||
|
if (commandHighlighter != null) {
|
||||||
|
asb.append(commandHighlighter.reset().highlight(command));
|
||||||
|
} else {
|
||||||
|
asb.append(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class FileHighlightCommand {
|
||||||
|
private final String subcommand;
|
||||||
|
private final List<String> fileOptions = new ArrayList<>();
|
||||||
|
|
||||||
|
public FileHighlightCommand() {
|
||||||
|
this(null, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileHighlightCommand(String subcommand, Collection<String> fileOptions) {
|
||||||
|
this.subcommand = subcommand;
|
||||||
|
this.fileOptions.addAll(fileOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSubcommand() {
|
||||||
|
return subcommand != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasFileOptions() {
|
||||||
|
return !fileOptions.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubcommand() {
|
||||||
|
return subcommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getFileOptions() {
|
||||||
|
return fileOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2087
net-cli/src/main/java/org/jline/console/impl/SystemRegistryImpl.java
Normal file
2087
net-cli/src/main/java/org/jline/console/impl/SystemRegistryImpl.java
Normal file
File diff suppressed because it is too large
Load diff
235
net-cli/src/main/java/org/jline/keymap/BindingReader.java
Normal file
235
net-cli/src/main/java/org/jline/keymap/BindingReader.java
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.keymap;
|
||||||
|
|
||||||
|
import java.io.IOError;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import org.jline.reader.EndOfFileException;
|
||||||
|
import org.jline.utils.ClosedException;
|
||||||
|
import org.jline.utils.NonBlockingReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BindingReader will transform incoming chars into
|
||||||
|
* key bindings
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
|
||||||
|
*/
|
||||||
|
public class BindingReader {
|
||||||
|
|
||||||
|
protected final NonBlockingReader reader;
|
||||||
|
protected final StringBuilder opBuffer = new StringBuilder();
|
||||||
|
protected final Deque<Integer> pushBackChar = new ArrayDeque<>();
|
||||||
|
protected String lastBinding;
|
||||||
|
|
||||||
|
public BindingReader(NonBlockingReader reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from the input stream and decode an operation from the key map.
|
||||||
|
* <p>
|
||||||
|
* The input stream will be read character by character until a matching
|
||||||
|
* binding can be found. Characters that can't possibly be matched to
|
||||||
|
* any binding will be send with the {@link KeyMap#getNomatch()} binding.
|
||||||
|
* Unicode (>= 128) characters will be matched to {@link KeyMap#getUnicode()}.
|
||||||
|
* If the current key sequence is ambiguous, i.e. the sequence is bound but
|
||||||
|
* it's also a prefix to other sequences, then the {@link KeyMap#getAmbiguousTimeout()}
|
||||||
|
* timeout will be used to wait for another incoming character.
|
||||||
|
* If a character comes, the disambiguation will be done. If the timeout elapses
|
||||||
|
* and no character came in, or if the timeout is <= 0, the current bound operation
|
||||||
|
* will be returned.
|
||||||
|
*
|
||||||
|
* @param keys the KeyMap to use for decoding the input stream
|
||||||
|
* @param <T> the type of bindings to be read
|
||||||
|
* @return the decoded binding or <code>null</code> if the end of
|
||||||
|
* stream has been reached
|
||||||
|
*/
|
||||||
|
public <T> T readBinding(KeyMap<T> keys) {
|
||||||
|
return readBinding(keys, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T readBinding(KeyMap<T> keys, KeyMap<T> local) {
|
||||||
|
return readBinding(keys, local, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T readBinding(KeyMap<T> keys, KeyMap<T> local, boolean block) {
|
||||||
|
lastBinding = null;
|
||||||
|
T o = null;
|
||||||
|
int[] remaining = new int[1];
|
||||||
|
boolean hasRead = false;
|
||||||
|
for (; ; ) {
|
||||||
|
if (local != null) {
|
||||||
|
o = local.getBound(opBuffer, remaining);
|
||||||
|
}
|
||||||
|
if (o == null && (local == null || remaining[0] >= 0)) {
|
||||||
|
o = keys.getBound(opBuffer, remaining);
|
||||||
|
}
|
||||||
|
// We have a binding and additional chars
|
||||||
|
if (o != null) {
|
||||||
|
if (remaining[0] >= 0) {
|
||||||
|
runMacro(opBuffer.substring(opBuffer.length() - remaining[0]));
|
||||||
|
opBuffer.setLength(opBuffer.length() - remaining[0]);
|
||||||
|
} else {
|
||||||
|
long ambiguousTimeout = keys.getAmbiguousTimeout();
|
||||||
|
if (ambiguousTimeout > 0 && peekCharacter(ambiguousTimeout) != NonBlockingReader.READ_EXPIRED) {
|
||||||
|
o = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o != null) {
|
||||||
|
lastBinding = opBuffer.toString();
|
||||||
|
opBuffer.setLength(0);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
// We don't match anything
|
||||||
|
} else if (remaining[0] > 0) {
|
||||||
|
int cp = opBuffer.codePointAt(0);
|
||||||
|
String rem = opBuffer.substring(Character.charCount(cp));
|
||||||
|
lastBinding = opBuffer.substring(0, Character.charCount(cp));
|
||||||
|
// Unicode character
|
||||||
|
o = (cp >= KeyMap.KEYMAP_LENGTH) ? keys.getUnicode() : keys.getNomatch();
|
||||||
|
opBuffer.setLength(0);
|
||||||
|
opBuffer.append(rem);
|
||||||
|
if (o != null) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!block && hasRead) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int c = readCharacter();
|
||||||
|
if (c == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
opBuffer.appendCodePoint(c);
|
||||||
|
hasRead = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readStringUntil(String sequence) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (!pushBackChar.isEmpty()) {
|
||||||
|
pushBackChar.forEach(sb::appendCodePoint);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
char[] buf = new char[64];
|
||||||
|
while (true) {
|
||||||
|
int idx = sb.indexOf(sequence, Math.max(0, sb.length() - buf.length - sequence.length()));
|
||||||
|
if (idx >= 0) {
|
||||||
|
String rem = sb.substring(idx + sequence.length());
|
||||||
|
runMacro(rem);
|
||||||
|
return sb.substring(0, idx);
|
||||||
|
}
|
||||||
|
int l = reader.readBuffered(buf);
|
||||||
|
if (l < 0) {
|
||||||
|
throw new ClosedException();
|
||||||
|
}
|
||||||
|
sb.append(buf, 0, l);
|
||||||
|
}
|
||||||
|
} catch (ClosedException e) {
|
||||||
|
throw new EndOfFileException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a codepoint from the terminal.
|
||||||
|
*
|
||||||
|
* @return the character, or -1 if an EOF is received.
|
||||||
|
*/
|
||||||
|
public int readCharacter() {
|
||||||
|
if (!pushBackChar.isEmpty()) {
|
||||||
|
return pushBackChar.pop();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int c = NonBlockingReader.READ_EXPIRED;
|
||||||
|
int s = 0;
|
||||||
|
while (c == NonBlockingReader.READ_EXPIRED) {
|
||||||
|
c = reader.read(100L);
|
||||||
|
if (c >= 0 && Character.isHighSurrogate((char) c)) {
|
||||||
|
s = c;
|
||||||
|
c = NonBlockingReader.READ_EXPIRED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s != 0 ? Character.toCodePoint((char) s, (char) c) : c;
|
||||||
|
} catch (ClosedException e) {
|
||||||
|
throw new EndOfFileException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readCharacterBuffered() {
|
||||||
|
try {
|
||||||
|
if (pushBackChar.isEmpty()) {
|
||||||
|
char[] buf = new char[32];
|
||||||
|
int l = reader.readBuffered(buf);
|
||||||
|
if (l <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int s = 0;
|
||||||
|
for (int i = 0; i < l; ) {
|
||||||
|
int c = buf[i++];
|
||||||
|
if (Character.isHighSurrogate((char) c)) {
|
||||||
|
s = c;
|
||||||
|
if (i < l) {
|
||||||
|
c = buf[i++];
|
||||||
|
pushBackChar.addLast(Character.toCodePoint((char) s, (char) c));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s = 0;
|
||||||
|
pushBackChar.addLast(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s != 0) {
|
||||||
|
int c = reader.read();
|
||||||
|
if (c >= 0) {
|
||||||
|
pushBackChar.addLast(Character.toCodePoint((char) s, (char) c));
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pushBackChar.pop();
|
||||||
|
} catch (ClosedException e) {
|
||||||
|
throw new EndOfFileException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int peekCharacter(long timeout) {
|
||||||
|
if (!pushBackChar.isEmpty()) {
|
||||||
|
return pushBackChar.peek();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return reader.peek(timeout);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runMacro(String macro) {
|
||||||
|
macro.codePoints().forEachOrdered(pushBackChar::addLast);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentBuffer() {
|
||||||
|
return opBuffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastBinding() {
|
||||||
|
return lastBinding;
|
||||||
|
}
|
||||||
|
}
|
450
net-cli/src/main/java/org/jline/keymap/KeyMap.java
Normal file
450
net-cli/src/main/java/org/jline/keymap/KeyMap.java
Normal file
|
@ -0,0 +1,450 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.keymap;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.utils.Curses;
|
||||||
|
import org.jline.utils.InfoCmp.Capability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The KeyMap class contains all bindings from keys to operations.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
|
||||||
|
* @since 2.6
|
||||||
|
*/
|
||||||
|
public class KeyMap<T> {
|
||||||
|
|
||||||
|
public static final int KEYMAP_LENGTH = 128;
|
||||||
|
public static final long DEFAULT_AMBIGUOUS_TIMEOUT = 1000L;
|
||||||
|
public static final Comparator<String> KEYSEQ_COMPARATOR = (s1, s2) -> {
|
||||||
|
int len1 = s1.length();
|
||||||
|
int len2 = s2.length();
|
||||||
|
int lim = Math.min(len1, len2);
|
||||||
|
int k = 0;
|
||||||
|
while (k < lim) {
|
||||||
|
char c1 = s1.charAt(k);
|
||||||
|
char c2 = s2.charAt(k);
|
||||||
|
if (c1 != c2) {
|
||||||
|
int l = len1 - len2;
|
||||||
|
return l != 0 ? l : c1 - c2;
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
return len1 - len2;
|
||||||
|
};
|
||||||
|
private final Object[] mapping = new Object[KEYMAP_LENGTH];
|
||||||
|
private T anotherKey = null;
|
||||||
|
private T unicode;
|
||||||
|
private T nomatch;
|
||||||
|
private long ambiguousTimeout = DEFAULT_AMBIGUOUS_TIMEOUT;
|
||||||
|
|
||||||
|
public static String display(String key) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("\"");
|
||||||
|
for (int i = 0; i < key.length(); i++) {
|
||||||
|
char c = key.charAt(i);
|
||||||
|
if (c < 32) {
|
||||||
|
sb.append('^');
|
||||||
|
sb.append((char) (c + 'A' - 1));
|
||||||
|
} else if (c == 127) {
|
||||||
|
sb.append("^?");
|
||||||
|
} else if (c == '^' || c == '\\') {
|
||||||
|
sb.append('\\').append(c);
|
||||||
|
} else if (c >= 128) {
|
||||||
|
sb.append(String.format("\\u%04x", (int) c));
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append("\"");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String translate(String str) {
|
||||||
|
int i;
|
||||||
|
if (!str.isEmpty()) {
|
||||||
|
char c = str.charAt(0);
|
||||||
|
if ((c == '\'' || c == '"') && str.charAt(str.length() - 1) == c) {
|
||||||
|
str = str.substring(1, str.length() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringBuilder keySeq = new StringBuilder();
|
||||||
|
for (i = 0; i < str.length(); i++) {
|
||||||
|
char c = str.charAt(i);
|
||||||
|
if (c == '\\') {
|
||||||
|
if (++i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = str.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case 'a':
|
||||||
|
c = 0x07;
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
c = '\b';
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
c = 0x7f;
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
c = 0x1b;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
c = '\f';
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
c = '\n';
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
c = '\r';
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
c = '\t';
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
c = 0x0b;
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
c = '\\';
|
||||||
|
break;
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
c = 0;
|
||||||
|
for (int j = 0; j < 3; j++, i++) {
|
||||||
|
if (i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int k = Character.digit(str.charAt(i), 8);
|
||||||
|
if (k < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = (char) (c * 8 + k);
|
||||||
|
}
|
||||||
|
i--;
|
||||||
|
c &= 0xFF;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
i++;
|
||||||
|
c = 0;
|
||||||
|
for (int j = 0; j < 2; j++, i++) {
|
||||||
|
if (i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int k = Character.digit(str.charAt(i), 16);
|
||||||
|
if (k < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = (char) (c * 16 + k);
|
||||||
|
}
|
||||||
|
i--;
|
||||||
|
c &= 0xFF;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
i++;
|
||||||
|
c = 0;
|
||||||
|
for (int j = 0; j < 4; j++, i++) {
|
||||||
|
if (i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int k = Character.digit(str.charAt(i), 16);
|
||||||
|
if (k < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = (char) (c * 16 + k);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
if (++i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = str.charAt(i);
|
||||||
|
if (c == '-') {
|
||||||
|
if (++i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = str.charAt(i);
|
||||||
|
}
|
||||||
|
c = c == '?' ? 0x7f : (char) (Character.toUpperCase(c) & 0x1f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (c == '^') {
|
||||||
|
if (++i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = str.charAt(i);
|
||||||
|
if (c != '^') {
|
||||||
|
c = c == '?' ? 0x7f : (char) (Character.toUpperCase(c) & 0x1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keySeq.append(c);
|
||||||
|
}
|
||||||
|
return keySeq.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<String> range(String range) {
|
||||||
|
String[] keys = range.split("-");
|
||||||
|
if (keys.length != 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
keys[0] = translate(keys[0]);
|
||||||
|
keys[1] = translate(keys[1]);
|
||||||
|
if (keys[0].length() != keys[1].length()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String pfx;
|
||||||
|
if (keys[0].length() > 1) {
|
||||||
|
pfx = keys[0].substring(0, keys[0].length() - 1);
|
||||||
|
if (!keys[1].startsWith(pfx)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pfx = "";
|
||||||
|
}
|
||||||
|
char c0 = keys[0].charAt(keys[0].length() - 1);
|
||||||
|
char c1 = keys[1].charAt(keys[1].length() - 1);
|
||||||
|
if (c0 > c1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Collection<String> seqs = new ArrayList<>();
|
||||||
|
for (char c = c0; c <= c1; c++) {
|
||||||
|
seqs.add(pfx + c);
|
||||||
|
}
|
||||||
|
return seqs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String esc() {
|
||||||
|
return "\033";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String alt(char c) {
|
||||||
|
return "\033" + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String alt(String c) {
|
||||||
|
return "\033" + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String del() {
|
||||||
|
return "\177";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String ctrl(char key) {
|
||||||
|
return key == '?' ? del() : Character.toString((char) (Character.toUpperCase(key) & 0x1f));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String key(Terminal terminal, Capability capability) {
|
||||||
|
return Curses.tputs(terminal.getStringCapability(capability));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Methods
|
||||||
|
//
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> void doGetBoundKeys(KeyMap<T> keyMap, String prefix, Map<String, T> bound) {
|
||||||
|
if (keyMap.anotherKey != null) {
|
||||||
|
bound.put(prefix, keyMap.anotherKey);
|
||||||
|
}
|
||||||
|
for (int c = 0; c < keyMap.mapping.length; c++) {
|
||||||
|
if (keyMap.mapping[c] instanceof KeyMap) {
|
||||||
|
doGetBoundKeys((KeyMap<T>) keyMap.mapping[c], prefix + (char) (c), bound);
|
||||||
|
} else if (keyMap.mapping[c] != null) {
|
||||||
|
bound.put(prefix + (char) (c), (T) keyMap.mapping[c]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> T unbind(KeyMap<T> map, CharSequence keySeq) {
|
||||||
|
KeyMap<T> prev = null;
|
||||||
|
if (keySeq != null && keySeq.length() > 0) {
|
||||||
|
for (int i = 0; i < keySeq.length() - 1; i++) {
|
||||||
|
char c = keySeq.charAt(i);
|
||||||
|
if (c > map.mapping.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!(map.mapping[c] instanceof KeyMap)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
prev = map;
|
||||||
|
map = (KeyMap<T>) map.mapping[c];
|
||||||
|
}
|
||||||
|
char c = keySeq.charAt(keySeq.length() - 1);
|
||||||
|
if (c > map.mapping.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (map.mapping[c] instanceof KeyMap) {
|
||||||
|
KeyMap<?> sub = (KeyMap) map.mapping[c];
|
||||||
|
Object res = sub.anotherKey;
|
||||||
|
sub.anotherKey = null;
|
||||||
|
return (T) res;
|
||||||
|
} else {
|
||||||
|
Object res = map.mapping[c];
|
||||||
|
map.mapping[c] = null;
|
||||||
|
int nb = 0;
|
||||||
|
for (int i = 0; i < map.mapping.length; i++) {
|
||||||
|
if (map.mapping[i] != null) {
|
||||||
|
nb++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nb == 0 && prev != null) {
|
||||||
|
prev.mapping[keySeq.charAt(keySeq.length() - 2)] = map.anotherKey;
|
||||||
|
}
|
||||||
|
return (T) res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> void bind(KeyMap<T> map, CharSequence keySeq, T function, boolean onlyIfNotBound) {
|
||||||
|
if (keySeq != null && keySeq.length() > 0) {
|
||||||
|
for (int i = 0; i < keySeq.length(); i++) {
|
||||||
|
char c = keySeq.charAt(i);
|
||||||
|
if (c >= map.mapping.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (i < keySeq.length() - 1) {
|
||||||
|
if (!(map.mapping[c] instanceof KeyMap)) {
|
||||||
|
KeyMap<T> m = new KeyMap<>();
|
||||||
|
m.anotherKey = (T) map.mapping[c];
|
||||||
|
map.mapping[c] = m;
|
||||||
|
}
|
||||||
|
map = (KeyMap) map.mapping[c];
|
||||||
|
} else {
|
||||||
|
if (map.mapping[c] instanceof KeyMap) {
|
||||||
|
((KeyMap) map.mapping[c]).anotherKey = function;
|
||||||
|
} else {
|
||||||
|
Object op = map.mapping[c];
|
||||||
|
if (!onlyIfNotBound || op == null) {
|
||||||
|
map.mapping[c] = function;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getUnicode() {
|
||||||
|
return unicode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnicode(T unicode) {
|
||||||
|
this.unicode = unicode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getNomatch() {
|
||||||
|
return nomatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNomatch(T nomatch) {
|
||||||
|
this.nomatch = nomatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAmbiguousTimeout() {
|
||||||
|
return ambiguousTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmbiguousTimeout(long ambiguousTimeout) {
|
||||||
|
this.ambiguousTimeout = ambiguousTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getAnotherKey() {
|
||||||
|
return anotherKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, T> getBoundKeys() {
|
||||||
|
Map<String, T> bound = new TreeMap<>(KEYSEQ_COMPARATOR);
|
||||||
|
doGetBoundKeys(this, "", bound);
|
||||||
|
return bound;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getBound(CharSequence keySeq, int[] remaining) {
|
||||||
|
remaining[0] = -1;
|
||||||
|
if (keySeq != null && keySeq.length() > 0) {
|
||||||
|
char c = keySeq.charAt(0);
|
||||||
|
if (c >= mapping.length) {
|
||||||
|
remaining[0] = Character.codePointCount(keySeq, 0, keySeq.length());
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
if (mapping[c] instanceof KeyMap) {
|
||||||
|
CharSequence sub = keySeq.subSequence(1, keySeq.length());
|
||||||
|
return ((KeyMap<T>) mapping[c]).getBound(sub, remaining);
|
||||||
|
} else if (mapping[c] != null) {
|
||||||
|
remaining[0] = keySeq.length() - 1;
|
||||||
|
return (T) mapping[c];
|
||||||
|
} else {
|
||||||
|
remaining[0] = keySeq.length();
|
||||||
|
return anotherKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return anotherKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getBound(CharSequence keySeq) {
|
||||||
|
int[] remaining = new int[1];
|
||||||
|
T res = getBound(keySeq, remaining);
|
||||||
|
return remaining[0] <= 0 ? res : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bindIfNotBound(T function, CharSequence keySeq) {
|
||||||
|
if (function != null && keySeq != null) {
|
||||||
|
bind(this, keySeq, function, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(T function, CharSequence... keySeqs) {
|
||||||
|
for (CharSequence keySeq : keySeqs) {
|
||||||
|
bind(function, keySeq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(T function, Iterable<? extends CharSequence> keySeqs) {
|
||||||
|
for (CharSequence keySeq : keySeqs) {
|
||||||
|
bind(function, keySeq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(T function, CharSequence keySeq) {
|
||||||
|
if (keySeq != null) {
|
||||||
|
if (function == null) {
|
||||||
|
unbind(keySeq);
|
||||||
|
} else {
|
||||||
|
bind(this, keySeq, function, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unbind(CharSequence... keySeqs) {
|
||||||
|
for (CharSequence keySeq : keySeqs) {
|
||||||
|
unbind(keySeq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unbind(CharSequence keySeq) {
|
||||||
|
if (keySeq != null) {
|
||||||
|
unbind(this, keySeq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
net-cli/src/main/java/org/jline/reader/Binding.java
Normal file
21
net-cli/src/main/java/org/jline/reader/Binding.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for objects bound to key sequences.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
|
||||||
|
* @see Macro
|
||||||
|
* @see Reference
|
||||||
|
* @see Widget
|
||||||
|
* @see org.jline.keymap.KeyMap
|
||||||
|
*/
|
||||||
|
public interface Binding {
|
||||||
|
}
|
91
net-cli/src/main/java/org/jline/reader/Buffer.java
Normal file
91
net-cli/src/main/java/org/jline/reader/Buffer.java
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2017, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
public interface Buffer {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read access
|
||||||
|
*/
|
||||||
|
|
||||||
|
int cursor();
|
||||||
|
|
||||||
|
int atChar(int i);
|
||||||
|
|
||||||
|
int length();
|
||||||
|
|
||||||
|
int currChar();
|
||||||
|
|
||||||
|
int prevChar();
|
||||||
|
|
||||||
|
int nextChar();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Movement
|
||||||
|
*/
|
||||||
|
|
||||||
|
boolean cursor(int position);
|
||||||
|
|
||||||
|
int move(int num);
|
||||||
|
|
||||||
|
boolean up();
|
||||||
|
|
||||||
|
boolean down();
|
||||||
|
|
||||||
|
boolean moveXY(int dx, int dy);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Modification
|
||||||
|
*/
|
||||||
|
|
||||||
|
boolean clear();
|
||||||
|
|
||||||
|
boolean currChar(int c);
|
||||||
|
|
||||||
|
void write(int c);
|
||||||
|
|
||||||
|
void write(int c, boolean overTyping);
|
||||||
|
|
||||||
|
void write(CharSequence str);
|
||||||
|
|
||||||
|
void write(CharSequence str, boolean overTyping);
|
||||||
|
|
||||||
|
boolean backspace();
|
||||||
|
|
||||||
|
int backspace(int num);
|
||||||
|
|
||||||
|
boolean delete();
|
||||||
|
|
||||||
|
int delete(int num);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* String
|
||||||
|
*/
|
||||||
|
|
||||||
|
String substring(int start);
|
||||||
|
|
||||||
|
String substring(int start, int end);
|
||||||
|
|
||||||
|
String upToCursor();
|
||||||
|
|
||||||
|
String toString();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy
|
||||||
|
*/
|
||||||
|
|
||||||
|
Buffer copy();
|
||||||
|
|
||||||
|
void copyFrom(Buffer buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear any internal buffer.
|
||||||
|
*/
|
||||||
|
void zeroOut();
|
||||||
|
}
|
204
net-cli/src/main/java/org/jline/reader/Candidate.java
Normal file
204
net-cli/src/main/java/org/jline/reader/Candidate.java
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2019, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completion candidate.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
|
||||||
|
*/
|
||||||
|
public class Candidate implements Comparable<Candidate> {
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
private final String displ;
|
||||||
|
private final String group;
|
||||||
|
private final String descr;
|
||||||
|
private final String suffix;
|
||||||
|
private final String key;
|
||||||
|
private final boolean complete;
|
||||||
|
private final int sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple constructor with only a single String as an argument.
|
||||||
|
*
|
||||||
|
* @param value the candidate
|
||||||
|
*/
|
||||||
|
public Candidate(String value) {
|
||||||
|
this(value, value, null, null, null, null, true, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new Candidate.
|
||||||
|
*
|
||||||
|
* @param value the value
|
||||||
|
* @param displ the display string
|
||||||
|
* @param group the group
|
||||||
|
* @param descr the description
|
||||||
|
* @param suffix the suffix
|
||||||
|
* @param key the key
|
||||||
|
* @param complete the complete flag
|
||||||
|
* @param sort the sort flag
|
||||||
|
*/
|
||||||
|
public Candidate(
|
||||||
|
String value,
|
||||||
|
String displ,
|
||||||
|
String group,
|
||||||
|
String descr,
|
||||||
|
String suffix,
|
||||||
|
String key,
|
||||||
|
boolean complete,
|
||||||
|
int sort) {
|
||||||
|
this.value = Objects.requireNonNull(value);
|
||||||
|
this.displ = Objects.requireNonNull(displ);
|
||||||
|
this.group = group;
|
||||||
|
this.descr = descr;
|
||||||
|
this.suffix = suffix;
|
||||||
|
this.key = key;
|
||||||
|
this.complete = complete;
|
||||||
|
this.sort = sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new Candidate.
|
||||||
|
*
|
||||||
|
* @param value the value
|
||||||
|
* @param displ the display string
|
||||||
|
* @param group the group
|
||||||
|
* @param descr the description
|
||||||
|
* @param suffix the suffix
|
||||||
|
* @param key the key
|
||||||
|
* @param complete the complete flag
|
||||||
|
*/
|
||||||
|
public Candidate(
|
||||||
|
String value, String displ, String group, String descr, String suffix, String key, boolean complete) {
|
||||||
|
this(value, displ, group, descr, suffix, key, complete, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value that will be used for the actual completion.
|
||||||
|
* This string should not contain ANSI sequences.
|
||||||
|
*
|
||||||
|
* @return the value
|
||||||
|
*/
|
||||||
|
public String value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The string that will be displayed to the user.
|
||||||
|
* This string may contain ANSI sequences.
|
||||||
|
*
|
||||||
|
* @return the display string
|
||||||
|
*/
|
||||||
|
public String displ() {
|
||||||
|
return displ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The group name for this candidate.
|
||||||
|
* Candidates can be grouped together and this string is used
|
||||||
|
* as a key for the group and displayed to the user.
|
||||||
|
*
|
||||||
|
* @return the group
|
||||||
|
* @see LineReader.Option#GROUP
|
||||||
|
* @see LineReader.Option#AUTO_GROUP
|
||||||
|
*/
|
||||||
|
public String group() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of this candidate, usually a small help message
|
||||||
|
* to understand the meaning of this candidate.
|
||||||
|
* This string may contain ANSI sequences.
|
||||||
|
*
|
||||||
|
* @return the description
|
||||||
|
*/
|
||||||
|
public String descr() {
|
||||||
|
return descr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The suffix is added when this candidate is displayed.
|
||||||
|
* However, if the next character entered does not match,
|
||||||
|
* the suffix will be automatically removed.
|
||||||
|
* This string should not contain ANSI sequences.
|
||||||
|
*
|
||||||
|
* @return the suffix
|
||||||
|
* @see LineReader.Option#AUTO_REMOVE_SLASH
|
||||||
|
* @see LineReader#REMOVE_SUFFIX_CHARS
|
||||||
|
*/
|
||||||
|
public String suffix() {
|
||||||
|
return suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Candidates which have the same key will be merged together.
|
||||||
|
* For example, if a command has multiple aliases, they can be merged
|
||||||
|
* if they are using the same key.
|
||||||
|
*
|
||||||
|
* @return the key
|
||||||
|
*/
|
||||||
|
public String key() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean indicating whether this candidate is complete or
|
||||||
|
* if the completer may further expand the candidate value
|
||||||
|
* after this candidate has been selected.
|
||||||
|
* This can be the case when completing folders for example.
|
||||||
|
* If the candidate is complete and is selected, a space
|
||||||
|
* separator will be added.
|
||||||
|
*
|
||||||
|
* @return the completion flag
|
||||||
|
*/
|
||||||
|
public boolean complete() {
|
||||||
|
return complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integer used to override default sort logic.
|
||||||
|
*
|
||||||
|
* @return the sort int
|
||||||
|
*/
|
||||||
|
public int sort() {
|
||||||
|
return sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Candidate o) {
|
||||||
|
// If both candidates have same sort, use default behavior
|
||||||
|
if (sort == o.sort()) {
|
||||||
|
return value.compareTo(o.value);
|
||||||
|
} else {
|
||||||
|
return Integer.compare(sort, o.sort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Candidate candidate = (Candidate) o;
|
||||||
|
return Objects.equals(value, candidate.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Candidate{" + value + "}";
|
||||||
|
}
|
||||||
|
}
|
37
net-cli/src/main/java/org/jline/reader/Completer.java
Normal file
37
net-cli/src/main/java/org/jline/reader/Completer.java
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completer is the mechanism by which tab-completion candidates will be resolved.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||||
|
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||||
|
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
|
||||||
|
* @since 2.3
|
||||||
|
*/
|
||||||
|
public interface Completer {
|
||||||
|
/**
|
||||||
|
* Populates <i>candidates</i> with a list of possible completions for the <i>command line</i>.
|
||||||
|
* <p>
|
||||||
|
* The list of candidates will be sorted and filtered by the LineReader, so that
|
||||||
|
* the list of candidates displayed to the user will usually be smaller than
|
||||||
|
* the list given by the completer. Thus it is not necessary for the completer
|
||||||
|
* to do any matching based on the current buffer. On the contrary, in order
|
||||||
|
* for the typo matcher to work, all possible candidates for the word being
|
||||||
|
* completed should be returned.
|
||||||
|
*
|
||||||
|
* @param reader The line reader
|
||||||
|
* @param line The parsed command line
|
||||||
|
* @param candidates The {@link List} of candidates to populate
|
||||||
|
*/
|
||||||
|
void complete(LineReader reader, ParsedLine line, List<Candidate> candidates);
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extension of {@link ParsedLine} that, being aware of the quoting and escaping rules
|
||||||
|
* of the {@link Parser} that produced it, knows if and how a completion candidate
|
||||||
|
* should be escaped/quoted.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public interface CompletingParsedLine extends ParsedLine {
|
||||||
|
|
||||||
|
CharSequence escape(CharSequence candidate, boolean complete);
|
||||||
|
|
||||||
|
int rawWordCursor();
|
||||||
|
|
||||||
|
int rawWordLength();
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface CompletionMatcher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles completion matcher functions
|
||||||
|
*
|
||||||
|
* @param options LineReader options
|
||||||
|
* @param prefix invoked by complete-prefix or expand-or-complete-prefix widget
|
||||||
|
* @param line The parsed line within which completion has been requested
|
||||||
|
* @param caseInsensitive if completion is case insensitive or not
|
||||||
|
* @param errors number of errors accepted in matching
|
||||||
|
* @param originalGroupName value of JLineReader variable original-group-name
|
||||||
|
*/
|
||||||
|
void compile(
|
||||||
|
Map<LineReader.Option, Boolean> options,
|
||||||
|
boolean prefix,
|
||||||
|
CompletingParsedLine line,
|
||||||
|
boolean caseInsensitive,
|
||||||
|
int errors,
|
||||||
|
String originalGroupName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param candidates list of candidates
|
||||||
|
* @return a list of candidates that completion matcher matches
|
||||||
|
*/
|
||||||
|
List<Candidate> matches(List<Candidate> candidates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a candidate that have exact match, null if no exact match found
|
||||||
|
*/
|
||||||
|
Candidate exactMatch();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a common prefix of matched candidates
|
||||||
|
*/
|
||||||
|
String getCommonPrefix();
|
||||||
|
}
|
45
net-cli/src/main/java/org/jline/reader/EOFError.java
Normal file
45
net-cli/src/main/java/org/jline/reader/EOFError.java
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
public class EOFError extends SyntaxError {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final String missing;
|
||||||
|
private final int openBrackets;
|
||||||
|
private final String nextClosingBracket;
|
||||||
|
|
||||||
|
public EOFError(int line, int column, String message) {
|
||||||
|
this(line, column, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EOFError(int line, int column, String message, String missing) {
|
||||||
|
this(line, column, message, missing, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EOFError(int line, int column, String message, String missing, int openBrackets, String nextClosingBracket) {
|
||||||
|
super(line, column, message);
|
||||||
|
this.missing = missing;
|
||||||
|
this.openBrackets = openBrackets;
|
||||||
|
this.nextClosingBracket = nextClosingBracket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMissing() {
|
||||||
|
return missing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOpenBrackets() {
|
||||||
|
return openBrackets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNextClosingBracket() {
|
||||||
|
return nextClosingBracket;
|
||||||
|
}
|
||||||
|
}
|
20
net-cli/src/main/java/org/jline/reader/Editor.java
Normal file
20
net-cli/src/main/java/org/jline/reader/Editor.java
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2019, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface Editor {
|
||||||
|
void open(List<String> files) throws IOException;
|
||||||
|
|
||||||
|
void run() throws IOException;
|
||||||
|
|
||||||
|
void setRestricted(boolean restricted);
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown by {@link LineReader#readLine} when
|
||||||
|
* user the user types ctrl-D).
|
||||||
|
*/
|
||||||
|
public class EndOfFileException extends RuntimeException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 528485360925144689L;
|
||||||
|
private String partialLine;
|
||||||
|
|
||||||
|
public EndOfFileException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndOfFileException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndOfFileException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndOfFileException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndOfFileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndOfFileException partialLine(String partialLine) {
|
||||||
|
this.partialLine = partialLine;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPartialLine() {
|
||||||
|
return partialLine;
|
||||||
|
}
|
||||||
|
}
|
16
net-cli/src/main/java/org/jline/reader/Expander.java
Normal file
16
net-cli/src/main/java/org/jline/reader/Expander.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
public interface Expander {
|
||||||
|
|
||||||
|
String expandHistory(History history, String line);
|
||||||
|
|
||||||
|
String expandVar(String word);
|
||||||
|
}
|
44
net-cli/src/main/java/org/jline/reader/Highlighter.java
Normal file
44
net-cli/src/main/java/org/jline/reader/Highlighter.java
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
|
||||||
|
public interface Highlighter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight buffer
|
||||||
|
*
|
||||||
|
* @param reader LineReader
|
||||||
|
* @param buffer the buffer to be highlighted
|
||||||
|
* @return highlighted buffer
|
||||||
|
*/
|
||||||
|
AttributedString highlight(LineReader reader, String buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh highlight configuration
|
||||||
|
*/
|
||||||
|
default void refresh(LineReader reader) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set error pattern to be highlighted
|
||||||
|
*
|
||||||
|
* @param errorPattern error pattern to be highlighted
|
||||||
|
*/
|
||||||
|
void setErrorPattern(Pattern errorPattern);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set error index to be highlighted
|
||||||
|
*
|
||||||
|
* @param errorIndex error index to be highlighted
|
||||||
|
*/
|
||||||
|
void setErrorIndex(int errorIndex);
|
||||||
|
}
|
218
net-cli/src/main/java/org/jline/reader/History.java
Normal file
218
net-cli/src/main/java/org/jline/reader/History.java
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console history.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||||
|
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||||
|
* @since 2.3
|
||||||
|
*/
|
||||||
|
public interface History extends Iterable<History.Entry> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the history for the given reader.
|
||||||
|
*
|
||||||
|
* @param reader the reader to attach to
|
||||||
|
*/
|
||||||
|
void attach(LineReader reader);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load history.
|
||||||
|
*
|
||||||
|
* @throws IOException if a problem occurs
|
||||||
|
*/
|
||||||
|
void load() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save history.
|
||||||
|
*
|
||||||
|
* @throws IOException if a problem occurs
|
||||||
|
*/
|
||||||
|
void save() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write history to the file. If incremental only the events that are new since the last incremental operation to
|
||||||
|
* the file are added.
|
||||||
|
*
|
||||||
|
* @param file History file
|
||||||
|
* @param incremental If true incremental write operation is performed.
|
||||||
|
* @throws IOException if a problem occurs
|
||||||
|
*/
|
||||||
|
void write(Path file, boolean incremental) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append history to the file. If incremental only the events that are new since the last incremental operation to
|
||||||
|
* the file are added.
|
||||||
|
*
|
||||||
|
* @param file History file
|
||||||
|
* @param incremental If true incremental append operation is performed.
|
||||||
|
* @throws IOException if a problem occurs
|
||||||
|
*/
|
||||||
|
void append(Path file, boolean incremental) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read history from the file. If checkDuplicates is <code>true</code> only the events that
|
||||||
|
* are not contained within the internal list are added.
|
||||||
|
*
|
||||||
|
* @param file History file
|
||||||
|
* @param checkDuplicates If <code>true</code>, duplicate history entries will be discarded
|
||||||
|
* @throws IOException if a problem occurs
|
||||||
|
*/
|
||||||
|
void read(Path file, boolean checkDuplicates) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purge history.
|
||||||
|
*
|
||||||
|
* @throws IOException if a problem occurs
|
||||||
|
*/
|
||||||
|
void purge() throws IOException;
|
||||||
|
|
||||||
|
int size();
|
||||||
|
|
||||||
|
default boolean isEmpty() {
|
||||||
|
return size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index();
|
||||||
|
|
||||||
|
int first();
|
||||||
|
|
||||||
|
int last();
|
||||||
|
|
||||||
|
String get(int index);
|
||||||
|
|
||||||
|
default void add(String line) {
|
||||||
|
add(Instant.now(), line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(Instant time, String line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an entry should be persisted or not.
|
||||||
|
*
|
||||||
|
* @param entry the entry to check
|
||||||
|
* @return <code>true</code> if the given entry should be persisted, <code>false</code> otherwise
|
||||||
|
*/
|
||||||
|
default boolean isPersistable(Entry entry) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Entries
|
||||||
|
//
|
||||||
|
|
||||||
|
ListIterator<Entry> iterator(int index);
|
||||||
|
|
||||||
|
default ListIterator<Entry> iterator() {
|
||||||
|
return iterator(first());
|
||||||
|
}
|
||||||
|
|
||||||
|
default Iterator<Entry> reverseIterator() {
|
||||||
|
return reverseIterator(last());
|
||||||
|
}
|
||||||
|
|
||||||
|
default Iterator<Entry> reverseIterator(int index) {
|
||||||
|
return new Iterator<Entry>() {
|
||||||
|
private final ListIterator<Entry> it = iterator(index + 1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return it.hasPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entry next() {
|
||||||
|
return it.previous();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
it.remove();
|
||||||
|
resetIndex();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the content of the current buffer.
|
||||||
|
*
|
||||||
|
* @return the content of the current buffer
|
||||||
|
*/
|
||||||
|
String current();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Navigation
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the pointer to the previous element in the buffer.
|
||||||
|
*
|
||||||
|
* @return true if we successfully went to the previous element
|
||||||
|
*/
|
||||||
|
boolean previous();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the pointer to the next element in the buffer.
|
||||||
|
*
|
||||||
|
* @return true if we successfully went to the next element
|
||||||
|
*/
|
||||||
|
boolean next();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the history index to the first entry.
|
||||||
|
*
|
||||||
|
* @return Return false if there are no iterator in the history or if the
|
||||||
|
* history is already at the beginning.
|
||||||
|
*/
|
||||||
|
boolean moveToFirst();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This moves the history to the last entry. This entry is one position
|
||||||
|
* before the moveToEnd() position.
|
||||||
|
*
|
||||||
|
* @return Returns false if there were no history iterator or the history
|
||||||
|
* index was already at the last entry.
|
||||||
|
*/
|
||||||
|
boolean moveToLast();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to the specified index in the history
|
||||||
|
*
|
||||||
|
* @param index The index to move to.
|
||||||
|
* @return Returns true if the index was moved.
|
||||||
|
*/
|
||||||
|
boolean moveTo(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to the end of the history buffer. This will be a blank entry, after
|
||||||
|
* all of the other iterator.
|
||||||
|
*/
|
||||||
|
void moveToEnd();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset index after remove
|
||||||
|
*/
|
||||||
|
void resetIndex();
|
||||||
|
|
||||||
|
interface Entry {
|
||||||
|
int index();
|
||||||
|
|
||||||
|
Instant time();
|
||||||
|
|
||||||
|
String line();
|
||||||
|
}
|
||||||
|
}
|
829
net-cli/src/main/java/org/jline/reader/LineReader.java
Normal file
829
net-cli/src/main/java/org/jline/reader/LineReader.java
Normal file
|
@ -0,0 +1,829 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2023, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
import org.jline.keymap.KeyMap;
|
||||||
|
import org.jline.terminal.MouseEvent;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read lines from the console, with input editing.
|
||||||
|
*
|
||||||
|
* <h2>Thread safety</h2>
|
||||||
|
* The <code>LineReader</code> implementations are not thread safe,
|
||||||
|
* thus you should not attempt to use a single reader in several threads.
|
||||||
|
* Any attempt to call one of the <code>readLine</code> call while one is
|
||||||
|
* already executing in a different thread will immediately result in an
|
||||||
|
* <code>IllegalStateException</code> being thrown. Other calls may lead to
|
||||||
|
* unknown behaviors. There is one exception though: users are allowed to call
|
||||||
|
* {@link #printAbove(String)} or {@link #printAbove(AttributedString)} at
|
||||||
|
* any time to allow text to be printed above the current prompt.
|
||||||
|
*
|
||||||
|
* <h2>Prompt strings</h2>
|
||||||
|
* It is traditional for an interactive console-based program
|
||||||
|
* to print a short prompt string to signal that the user is expected
|
||||||
|
* to type a command. JLine supports 3 kinds of prompt string:
|
||||||
|
* <ul>
|
||||||
|
* <li> The normal prompt at the start (left) of the initial line of a command.
|
||||||
|
* <li> An optional right prompt at the right border of the initial line.
|
||||||
|
* <li> A start (left) prompt for continuation lines. I.e. the lines
|
||||||
|
* after the first line of a multi-line command.
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* All of these are specified with prompt templates,
|
||||||
|
* which are similar to {@code printf} format strings,
|
||||||
|
* using the character {@code '%'} to indicate special functionality.
|
||||||
|
* </p>
|
||||||
|
* The pattern may include ANSI escapes.
|
||||||
|
* It may include these template markers:
|
||||||
|
* <dl>
|
||||||
|
* <dt>{@code %N}</dt>
|
||||||
|
* <dd>A line number. This is the sum of {@code getLineNumber()}
|
||||||
|
* and a counter starting with 1 for the first continuation line.
|
||||||
|
* </dd>
|
||||||
|
* <dt>{@code %M}</dt>
|
||||||
|
* <dd>A short word explaining what is "missing". This is supplied from
|
||||||
|
* the {@link EOFError#getMissing()} method, if provided.
|
||||||
|
* Defaults to an empty string.
|
||||||
|
* </dd>
|
||||||
|
* <dt>{@code %}<var>n</var>{@code P}<var>c</var></dt>
|
||||||
|
* <dd>Insert padding at this position, repeating the following
|
||||||
|
* character <var>c</var> as needed to bring the total prompt
|
||||||
|
* column width as specified by the digits <var>n</var>.
|
||||||
|
* </dd>
|
||||||
|
* <dt>{@code %P}<var>c</var></dt>
|
||||||
|
* <dd>As before, but use width from the initial prompt.
|
||||||
|
* </dd>
|
||||||
|
* <dt>{@code %%}</dt>
|
||||||
|
* <dd>A literal {@code '%'}.
|
||||||
|
* </dd>
|
||||||
|
* <dt><code>%{</code></dt><dt><code>%}</code></dt>
|
||||||
|
* <dd>Text between a <code>%{</code>...<code>%}</code> pair is printed as
|
||||||
|
* part of a prompt, but not interpreted by JLine
|
||||||
|
* (except that {@code '%'}-escapes are processed). The text is assumed
|
||||||
|
* to take zero columns (not move the cursor). If it changes the style,
|
||||||
|
* you're responsible for changing it back. Standard ANSI escape sequences
|
||||||
|
* do not need to be within a <code>%{</code>...<code>%}</code> pair
|
||||||
|
* (though can be) since JLine knows how to deal with them. However,
|
||||||
|
* these delimiters are needed for unusual non-standard escape sequences.
|
||||||
|
* </dd>
|
||||||
|
* </dl>
|
||||||
|
*/
|
||||||
|
public interface LineReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System property that can be set to avoid a warning being logged
|
||||||
|
* when using a Parser which does not return {@link CompletingParsedLine} objects.
|
||||||
|
*/
|
||||||
|
String PROP_SUPPORT_PARSEDLINE = "org.jline.reader.support.parsedline";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Widget names
|
||||||
|
//
|
||||||
|
String CALLBACK_INIT = "callback-init";
|
||||||
|
String CALLBACK_FINISH = "callback-finish";
|
||||||
|
String CALLBACK_KEYMAP = "callback-keymap";
|
||||||
|
|
||||||
|
String ACCEPT_AND_INFER_NEXT_HISTORY = "accept-and-infer-next-history";
|
||||||
|
String ACCEPT_AND_HOLD = "accept-and-hold";
|
||||||
|
String ACCEPT_LINE = "accept-line";
|
||||||
|
String ACCEPT_LINE_AND_DOWN_HISTORY = "accept-line-and-down-history";
|
||||||
|
String ARGUMENT_BASE = "argument-base";
|
||||||
|
String BACKWARD_CHAR = "backward-char";
|
||||||
|
String BACKWARD_DELETE_CHAR = "backward-delete-char";
|
||||||
|
String BACKWARD_DELETE_WORD = "backward-delete-word";
|
||||||
|
String BACKWARD_KILL_LINE = "backward-kill-line";
|
||||||
|
String BACKWARD_KILL_WORD = "backward-kill-word";
|
||||||
|
String BACKWARD_WORD = "backward-word";
|
||||||
|
String BEEP = "beep";
|
||||||
|
String BEGINNING_OF_BUFFER_OR_HISTORY = "beginning-of-buffer-or-history";
|
||||||
|
String BEGINNING_OF_HISTORY = "beginning-of-history";
|
||||||
|
String BEGINNING_OF_LINE = "beginning-of-line";
|
||||||
|
String BEGINNING_OF_LINE_HIST = "beginning-of-line-hist";
|
||||||
|
String CAPITALIZE_WORD = "capitalize-word";
|
||||||
|
String CHARACTER_SEARCH = "character-search";
|
||||||
|
String CHARACTER_SEARCH_BACKWARD = "character-search-backward";
|
||||||
|
String CLEAR = "clear";
|
||||||
|
String CLEAR_SCREEN = "clear-screen";
|
||||||
|
String COMPLETE_PREFIX = "complete-prefix";
|
||||||
|
String COMPLETE_WORD = "complete-word";
|
||||||
|
String COPY_PREV_WORD = "copy-prev-word";
|
||||||
|
String COPY_REGION_AS_KILL = "copy-region-as-kill";
|
||||||
|
String DELETE_CHAR = "delete-char";
|
||||||
|
String DELETE_CHAR_OR_LIST = "delete-char-or-list";
|
||||||
|
String DELETE_WORD = "delete-word";
|
||||||
|
String DIGIT_ARGUMENT = "digit-argument";
|
||||||
|
String DO_LOWERCASE_VERSION = "do-lowercase-version";
|
||||||
|
String DOWN_CASE_WORD = "down-case-word";
|
||||||
|
String DOWN_HISTORY = "down-history";
|
||||||
|
String DOWN_LINE = "down-line";
|
||||||
|
String DOWN_LINE_OR_HISTORY = "down-line-or-history";
|
||||||
|
String DOWN_LINE_OR_SEARCH = "down-line-or-search";
|
||||||
|
String EDIT_AND_EXECUTE_COMMAND = "edit-and-execute-command";
|
||||||
|
String EMACS_BACKWARD_WORD = "emacs-backward-word";
|
||||||
|
String EMACS_EDITING_MODE = "emacs-editing-mode";
|
||||||
|
String EMACS_FORWARD_WORD = "emacs-forward-word";
|
||||||
|
String END_OF_BUFFER_OR_HISTORY = "end-of-buffer-or-history";
|
||||||
|
String END_OF_HISTORY = "end-of-history";
|
||||||
|
String END_OF_LINE = "end-of-line";
|
||||||
|
String END_OF_LINE_HIST = "end-of-line-hist";
|
||||||
|
String EXCHANGE_POINT_AND_MARK = "exchange-point-and-mark";
|
||||||
|
String EXECUTE_NAMED_CMD = "execute-named-cmd";
|
||||||
|
String EXPAND_HISTORY = "expand-history";
|
||||||
|
String EXPAND_OR_COMPLETE = "expand-or-complete";
|
||||||
|
String EXPAND_OR_COMPLETE_PREFIX = "expand-or-complete-prefix";
|
||||||
|
String EXPAND_WORD = "expand-word";
|
||||||
|
String FRESH_LINE = "fresh-line";
|
||||||
|
String FORWARD_CHAR = "forward-char";
|
||||||
|
String FORWARD_WORD = "forward-word";
|
||||||
|
String HISTORY_BEGINNING_SEARCH_BACKWARD = "history-beginning-search-backward";
|
||||||
|
String HISTORY_BEGINNING_SEARCH_FORWARD = "history-beginning-search-forward";
|
||||||
|
String HISTORY_INCREMENTAL_PATTERN_SEARCH_BACKWARD = "history-incremental-pattern-search-backward";
|
||||||
|
String HISTORY_INCREMENTAL_PATTERN_SEARCH_FORWARD = "history-incremental-pattern-search-forward";
|
||||||
|
String HISTORY_INCREMENTAL_SEARCH_BACKWARD = "history-incremental-search-backward";
|
||||||
|
String HISTORY_INCREMENTAL_SEARCH_FORWARD = "history-incremental-search-forward";
|
||||||
|
String HISTORY_SEARCH_BACKWARD = "history-search-backward";
|
||||||
|
String HISTORY_SEARCH_FORWARD = "history-search-forward";
|
||||||
|
String INSERT_CLOSE_CURLY = "insert-close-curly";
|
||||||
|
String INSERT_CLOSE_PAREN = "insert-close-paren";
|
||||||
|
String INSERT_CLOSE_SQUARE = "insert-close-square";
|
||||||
|
String INFER_NEXT_HISTORY = "infer-next-history";
|
||||||
|
String INSERT_COMMENT = "insert-comment";
|
||||||
|
String INSERT_LAST_WORD = "insert-last-word";
|
||||||
|
String KILL_BUFFER = "kill-buffer";
|
||||||
|
String KILL_LINE = "kill-line";
|
||||||
|
String KILL_REGION = "kill-region";
|
||||||
|
String KILL_WHOLE_LINE = "kill-whole-line";
|
||||||
|
String KILL_WORD = "kill-word";
|
||||||
|
String LIST_CHOICES = "list-choices";
|
||||||
|
String LIST_EXPAND = "list-expand";
|
||||||
|
String MAGIC_SPACE = "magic-space";
|
||||||
|
String MENU_EXPAND_OR_COMPLETE = "menu-expand-or-complete";
|
||||||
|
String MENU_COMPLETE = "menu-complete";
|
||||||
|
String MENU_SELECT = "menu-select";
|
||||||
|
String NEG_ARGUMENT = "neg-argument";
|
||||||
|
String OVERWRITE_MODE = "overwrite-mode";
|
||||||
|
String PUT_REPLACE_SELECTION = "put-replace-selection";
|
||||||
|
String QUOTED_INSERT = "quoted-insert";
|
||||||
|
String READ_COMMAND = "read-command";
|
||||||
|
String RECURSIVE_EDIT = "recursive-edit";
|
||||||
|
String REDISPLAY = "redisplay";
|
||||||
|
String REDRAW_LINE = "redraw-line";
|
||||||
|
String REDO = "redo";
|
||||||
|
String REVERSE_MENU_COMPLETE = "reverse-menu-complete";
|
||||||
|
String SELF_INSERT = "self-insert";
|
||||||
|
String SELF_INSERT_UNMETA = "self-insert-unmeta";
|
||||||
|
String SEND_BREAK = "abort";
|
||||||
|
String SET_LOCAL_HISTORY = "set-local-history";
|
||||||
|
String SET_MARK_COMMAND = "set-mark-command";
|
||||||
|
String SPELL_WORD = "spell-word";
|
||||||
|
String SPLIT_UNDO = "split-undo";
|
||||||
|
String TRANSPOSE_CHARS = "transpose-chars";
|
||||||
|
String TRANSPOSE_WORDS = "transpose-words";
|
||||||
|
String UNDEFINED_KEY = "undefined-key";
|
||||||
|
String UNDO = "undo";
|
||||||
|
String UNIVERSAL_ARGUMENT = "universal-argument";
|
||||||
|
String UP_CASE_WORD = "up-case-word";
|
||||||
|
String UP_HISTORY = "up-history";
|
||||||
|
String UP_LINE = "up-line";
|
||||||
|
String UP_LINE_OR_HISTORY = "up-line-or-history";
|
||||||
|
String UP_LINE_OR_SEARCH = "up-line-or-search";
|
||||||
|
String VI_ADD_EOL = "vi-add-eol";
|
||||||
|
String VI_ADD_NEXT = "vi-add-next";
|
||||||
|
String VI_BACKWARD_BLANK_WORD = "vi-backward-blank-word";
|
||||||
|
String VI_BACKWARD_BLANK_WORD_END = "vi-backward-blank-word-end";
|
||||||
|
String VI_BACKWARD_CHAR = "vi-backward-char";
|
||||||
|
String VI_BACKWARD_DELETE_CHAR = "vi-backward-delete-char";
|
||||||
|
String VI_BACKWARD_KILL_WORD = "vi-backward-kill-word";
|
||||||
|
String VI_BACKWARD_WORD = "vi-backward-word";
|
||||||
|
String VI_BACKWARD_WORD_END = "vi-backward-word-end";
|
||||||
|
String VI_BEGINNING_OF_LINE = "vi-beginning-of-line";
|
||||||
|
String VI_CHANGE = "vi-change-to";
|
||||||
|
String VI_CHANGE_EOL = "vi-change-eol";
|
||||||
|
String VI_CHANGE_WHOLE_LINE = "vi-change-whole-line";
|
||||||
|
String VI_CMD_MODE = "vi-cmd-mode";
|
||||||
|
String VI_DELETE = "vi-delete";
|
||||||
|
String VI_DELETE_CHAR = "vi-delete-char";
|
||||||
|
String VI_DIGIT_OR_BEGINNING_OF_LINE = "vi-digit-or-beginning-of-line";
|
||||||
|
String VI_DOWN_LINE_OR_HISTORY = "vi-down-line-or-history";
|
||||||
|
String VI_END_OF_LINE = "vi-end-of-line";
|
||||||
|
String VI_FETCH_HISTORY = "vi-fetch-history";
|
||||||
|
String VI_FIND_NEXT_CHAR = "vi-find-next-char";
|
||||||
|
String VI_FIND_NEXT_CHAR_SKIP = "vi-find-next-char-skip";
|
||||||
|
String VI_FIND_PREV_CHAR = "vi-find-prev-char";
|
||||||
|
String VI_FIND_PREV_CHAR_SKIP = "vi-find-prev-char-skip";
|
||||||
|
String VI_FIRST_NON_BLANK = "vi-first-non-blank";
|
||||||
|
String VI_FORWARD_BLANK_WORD = "vi-forward-blank-word";
|
||||||
|
String VI_FORWARD_BLANK_WORD_END = "vi-forward-blank-word-end";
|
||||||
|
String VI_FORWARD_CHAR = "vi-forward-char";
|
||||||
|
String VI_FORWARD_WORD = "vi-forward-word";
|
||||||
|
String VI_FORWARD_WORD_END = "vi-forward-word-end";
|
||||||
|
String VI_GOTO_COLUMN = "vi-goto-column";
|
||||||
|
String VI_HISTORY_SEARCH_BACKWARD = "vi-history-search-backward";
|
||||||
|
String VI_HISTORY_SEARCH_FORWARD = "vi-history-search-forward";
|
||||||
|
String VI_INSERT = "vi-insert";
|
||||||
|
String VI_INSERT_BOL = "vi-insert-bol";
|
||||||
|
String VI_INSERT_COMMENT = "vi-insert-comment";
|
||||||
|
String VI_JOIN = "vi-join";
|
||||||
|
String VI_KILL_EOL = "vi-kill-eol";
|
||||||
|
String VI_KILL_LINE = "vi-kill-line";
|
||||||
|
String VI_MATCH_BRACKET = "vi-match-bracket";
|
||||||
|
String VI_OPEN_LINE_ABOVE = "vi-open-line-above";
|
||||||
|
String VI_OPEN_LINE_BELOW = "vi-open-line-below";
|
||||||
|
String VI_OPER_SWAP_CASE = "vi-oper-swap-case";
|
||||||
|
String VI_PUT_AFTER = "vi-put-after";
|
||||||
|
String VI_PUT_BEFORE = "vi-put-before";
|
||||||
|
String VI_QUOTED_INSERT = "vi-quoted-insert";
|
||||||
|
String VI_REPEAT_CHANGE = "vi-repeat-change";
|
||||||
|
String VI_REPEAT_FIND = "vi-repeat-find";
|
||||||
|
String VI_REPEAT_SEARCH = "vi-repeat-search";
|
||||||
|
String VI_REPLACE = "vi-replace";
|
||||||
|
String VI_REPLACE_CHARS = "vi-replace-chars";
|
||||||
|
String VI_REV_REPEAT_FIND = "vi-rev-repeat-find";
|
||||||
|
String VI_REV_REPEAT_SEARCH = "vi-rev-repeat-search";
|
||||||
|
String VI_SET_BUFFER = "vi-set-buffer";
|
||||||
|
String VI_SUBSTITUTE = "vi-substitute";
|
||||||
|
String VI_SWAP_CASE = "vi-swap-case";
|
||||||
|
String VI_UNDO_CHANGE = "vi-undo-change";
|
||||||
|
String VI_UP_LINE_OR_HISTORY = "vi-up-line-or-history";
|
||||||
|
String VI_YANK = "vi-yank";
|
||||||
|
String VI_YANK_EOL = "vi-yank-eol";
|
||||||
|
String VI_YANK_WHOLE_LINE = "vi-yank-whole-line";
|
||||||
|
String VISUAL_LINE_MODE = "visual-line-mode";
|
||||||
|
String VISUAL_MODE = "visual-mode";
|
||||||
|
String WHAT_CURSOR_POSITION = "what-cursor-position";
|
||||||
|
String YANK = "yank";
|
||||||
|
String YANK_POP = "yank-pop";
|
||||||
|
String MOUSE = "mouse";
|
||||||
|
String FOCUS_IN = "terminal-focus-in";
|
||||||
|
String FOCUS_OUT = "terminal-focus-out";
|
||||||
|
|
||||||
|
String BEGIN_PASTE = "begin-paste";
|
||||||
|
|
||||||
|
//
|
||||||
|
// KeyMap names
|
||||||
|
//
|
||||||
|
|
||||||
|
String VICMD = "vicmd";
|
||||||
|
String VIINS = "viins";
|
||||||
|
String VIOPP = "viopp";
|
||||||
|
String VISUAL = "visual";
|
||||||
|
String MAIN = "main";
|
||||||
|
String EMACS = "emacs";
|
||||||
|
String SAFE = ".safe";
|
||||||
|
String DUMB = "dumb";
|
||||||
|
String MENU = "menu";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variable names
|
||||||
|
//
|
||||||
|
|
||||||
|
String BIND_TTY_SPECIAL_CHARS = "bind-tty-special-chars";
|
||||||
|
String COMMENT_BEGIN = "comment-begin";
|
||||||
|
String BELL_STYLE = "bell-style";
|
||||||
|
String PREFER_VISIBLE_BELL = "prefer-visible-bell";
|
||||||
|
/**
|
||||||
|
* tab completion: if candidates are more than list-max a question will be asked before displaying them
|
||||||
|
*/
|
||||||
|
String LIST_MAX = "list-max";
|
||||||
|
/**
|
||||||
|
* tab completion: if candidates are less than menu-list-max
|
||||||
|
* they are displayed in a list below the field to be completed
|
||||||
|
*/
|
||||||
|
String MENU_LIST_MAX = "menu-list-max";
|
||||||
|
|
||||||
|
String DISABLE_HISTORY = "disable-history";
|
||||||
|
String DISABLE_COMPLETION = "disable-completion";
|
||||||
|
String EDITING_MODE = "editing-mode";
|
||||||
|
String KEYMAP = "keymap";
|
||||||
|
String BLINK_MATCHING_PAREN = "blink-matching-paren";
|
||||||
|
String WORDCHARS = "WORDCHARS";
|
||||||
|
String REMOVE_SUFFIX_CHARS = "REMOVE_SUFFIX_CHARS";
|
||||||
|
String SEARCH_TERMINATORS = "search-terminators";
|
||||||
|
/**
|
||||||
|
* Number of matching errors that are accepted by the completion matcher
|
||||||
|
*/
|
||||||
|
String ERRORS = "errors";
|
||||||
|
/**
|
||||||
|
* Property for the "others" group name
|
||||||
|
*/
|
||||||
|
String OTHERS_GROUP_NAME = "OTHERS_GROUP_NAME";
|
||||||
|
/**
|
||||||
|
* Property for the "original" group name
|
||||||
|
*/
|
||||||
|
String ORIGINAL_GROUP_NAME = "ORIGINAL_GROUP_NAME";
|
||||||
|
/**
|
||||||
|
* Completion style for displaying groups name
|
||||||
|
*/
|
||||||
|
String COMPLETION_STYLE_GROUP = "COMPLETION_STYLE_GROUP";
|
||||||
|
|
||||||
|
String COMPLETION_STYLE_LIST_GROUP = "COMPLETION_STYLE_LIST_GROUP";
|
||||||
|
/**
|
||||||
|
* Completion style for displaying the current selected item
|
||||||
|
*/
|
||||||
|
String COMPLETION_STYLE_SELECTION = "COMPLETION_STYLE_SELECTION";
|
||||||
|
|
||||||
|
String COMPLETION_STYLE_LIST_SELECTION = "COMPLETION_STYLE_LIST_SELECTION";
|
||||||
|
/**
|
||||||
|
* Completion style for displaying the candidate description
|
||||||
|
*/
|
||||||
|
String COMPLETION_STYLE_DESCRIPTION = "COMPLETION_STYLE_DESCRIPTION";
|
||||||
|
|
||||||
|
String COMPLETION_STYLE_LIST_DESCRIPTION = "COMPLETION_STYLE_LIST_DESCRIPTION";
|
||||||
|
/**
|
||||||
|
* Completion style for displaying the matching part of candidates
|
||||||
|
*/
|
||||||
|
String COMPLETION_STYLE_STARTING = "COMPLETION_STYLE_STARTING";
|
||||||
|
|
||||||
|
String COMPLETION_STYLE_LIST_STARTING = "COMPLETION_STYLE_LIST_STARTING";
|
||||||
|
/**
|
||||||
|
* Completion style for displaying the list
|
||||||
|
*/
|
||||||
|
String COMPLETION_STYLE_BACKGROUND = "COMPLETION_STYLE_BACKGROUND";
|
||||||
|
|
||||||
|
String COMPLETION_STYLE_LIST_BACKGROUND = "COMPLETION_STYLE_LIST_BACKGROUND";
|
||||||
|
/**
|
||||||
|
* Set the template for prompts for secondary (continuation) lines.
|
||||||
|
* This is a prompt template as described in the class header.
|
||||||
|
*/
|
||||||
|
String SECONDARY_PROMPT_PATTERN = "secondary-prompt-pattern";
|
||||||
|
/**
|
||||||
|
* When in multiline edit mode, this variable can be used
|
||||||
|
* to offset the line number displayed.
|
||||||
|
*/
|
||||||
|
String LINE_OFFSET = "line-offset";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout for ambiguous key sequences.
|
||||||
|
* If the key sequence is ambiguous, i.e. there is a matching
|
||||||
|
* sequence but the sequence is also a prefix for other bindings,
|
||||||
|
* the next key press will be waited for a specified amount of
|
||||||
|
* time. If the timeout elapses, the matched sequence will be
|
||||||
|
* used.
|
||||||
|
*/
|
||||||
|
String AMBIGUOUS_BINDING = "ambiguous-binding";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colon separated list of patterns that will not be saved in history.
|
||||||
|
*/
|
||||||
|
String HISTORY_IGNORE = "history-ignore";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File system history path.
|
||||||
|
*/
|
||||||
|
String HISTORY_FILE = "history-file";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of history items to keep in memory.
|
||||||
|
*/
|
||||||
|
String HISTORY_SIZE = "history-size";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of history items to keep in the history file.
|
||||||
|
*/
|
||||||
|
String HISTORY_FILE_SIZE = "history-file-size";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New line automatic indentation after opening/closing bracket.
|
||||||
|
*/
|
||||||
|
String INDENTATION = "indentation";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max buffer size for advanced features.
|
||||||
|
* Once the length of the buffer reaches this threshold, no
|
||||||
|
* advanced features will be enabled. This includes the undo
|
||||||
|
* buffer, syntax highlighting, parsing, etc....
|
||||||
|
*/
|
||||||
|
String FEATURES_MAX_BUFFER_SIZE = "features-max-buffer-size";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Min buffer size for tab auto-suggestions.
|
||||||
|
* For shorter buffer sizes auto-suggestions are not resolved.
|
||||||
|
*/
|
||||||
|
String SUGGESTIONS_MIN_BUFFER_SIZE = "suggestions-min-buffer-size";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max number of times a command can be repeated.
|
||||||
|
*/
|
||||||
|
String MAX_REPEAT_COUNT = "max-repeat-count";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of spaces to display a tabulation, the default is 4.
|
||||||
|
*/
|
||||||
|
String TAB_WIDTH = "tab-width";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of inputrc to read at line reader creation time.
|
||||||
|
*/
|
||||||
|
String INPUT_RC_FILE_NAME = "input-rc-file-name";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefix to automatically delegate variables to system properties
|
||||||
|
*/
|
||||||
|
String SYSTEM_PROPERTY_PREFIX = "system-property-prefix";
|
||||||
|
|
||||||
|
Map<String, KeyMap<Binding>> defaultKeyMaps();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next line and return the contents of the buffer.
|
||||||
|
* <p>
|
||||||
|
* Equivalent to <code>readLine(null, null, null)</code>.
|
||||||
|
*
|
||||||
|
* @return the line read
|
||||||
|
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
|
||||||
|
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
|
||||||
|
* @throws java.io.IOError in case of other i/o errors
|
||||||
|
*/
|
||||||
|
String readLine() throws UserInterruptException, EndOfFileException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next line with the specified character mask. If null, then
|
||||||
|
* characters will be echoed. If 0, then no characters will be echoed.
|
||||||
|
* <p>
|
||||||
|
* Equivalent to <code>readLine(null, mask, null)</code>
|
||||||
|
*
|
||||||
|
* @param mask The mask character, <code>null</code> or <code>0</code>.
|
||||||
|
* @return A line that is read from the terminal, can never be null.
|
||||||
|
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
|
||||||
|
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
|
||||||
|
* @throws java.io.IOError in case of other i/o errors
|
||||||
|
*/
|
||||||
|
String readLine(Character mask) throws UserInterruptException, EndOfFileException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next line with the specified prompt.
|
||||||
|
* If null, then the default prompt will be used.
|
||||||
|
* <p>
|
||||||
|
* Equivalent to <code>readLine(prompt, null, null)</code>
|
||||||
|
*
|
||||||
|
* @param prompt The prompt to issue to the terminal, may be null.
|
||||||
|
* @return A line that is read from the terminal, can never be null.
|
||||||
|
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
|
||||||
|
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
|
||||||
|
* @throws java.io.IOError in case of other i/o errors
|
||||||
|
*/
|
||||||
|
String readLine(String prompt) throws UserInterruptException, EndOfFileException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a line from the <i>in</i> {@link InputStream}, and return the line
|
||||||
|
* (without any trailing newlines).
|
||||||
|
* <p>
|
||||||
|
* Equivalent to <code>readLine(prompt, mask, null)</code>
|
||||||
|
*
|
||||||
|
* @param prompt The prompt to issue to the terminal, may be null.
|
||||||
|
* @param mask The mask character, <code>null</code> or <code>0</code>.
|
||||||
|
* @return A line that is read from the terminal, can never be null.
|
||||||
|
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
|
||||||
|
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
|
||||||
|
* @throws java.io.IOError in case of other i/o errors
|
||||||
|
*/
|
||||||
|
String readLine(String prompt, Character mask) throws UserInterruptException, EndOfFileException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a line from the <i>in</i> {@link InputStream}, and return the line
|
||||||
|
* (without any trailing newlines).
|
||||||
|
* <p>
|
||||||
|
* Equivalent to <code>readLine(prompt, null, mask, buffer)</code>
|
||||||
|
*
|
||||||
|
* @param prompt The prompt to issue to the terminal, may be null.
|
||||||
|
* This is a template, with optional {@code '%'} escapes, as
|
||||||
|
* described in the class header.
|
||||||
|
* @param mask The character mask, may be null.
|
||||||
|
* @param buffer The default value presented to the user to edit, may be null.
|
||||||
|
* @return A line that is read from the terminal, can never be null.
|
||||||
|
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
|
||||||
|
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
|
||||||
|
* @throws java.io.IOError in case of other i/o errors
|
||||||
|
*/
|
||||||
|
String readLine(String prompt, Character mask, String buffer) throws UserInterruptException, EndOfFileException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a line from the <i>in</i> {@link InputStream}, and return the line
|
||||||
|
* (without any trailing newlines).
|
||||||
|
*
|
||||||
|
* @param prompt The prompt to issue to the terminal, may be null.
|
||||||
|
* This is a template, with optional {@code '%'} escapes, as
|
||||||
|
* described in the class header.
|
||||||
|
* @param rightPrompt The right prompt
|
||||||
|
* This is a template, with optional {@code '%'} escapes, as
|
||||||
|
* described in the class header.
|
||||||
|
* @param mask The character mask, may be null.
|
||||||
|
* @param buffer The default value presented to the user to edit, may be null.
|
||||||
|
* @return A line that is read from the terminal, can never be null.
|
||||||
|
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
|
||||||
|
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
|
||||||
|
* @throws java.io.IOError in case of other i/o errors
|
||||||
|
*/
|
||||||
|
String readLine(String prompt, String rightPrompt, Character mask, String buffer)
|
||||||
|
throws UserInterruptException, EndOfFileException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a line from the <i>in</i> {@link InputStream}, and return the line
|
||||||
|
* (without any trailing newlines).
|
||||||
|
*
|
||||||
|
* @param prompt The prompt to issue to the terminal, may be null.
|
||||||
|
* This is a template, with optional {@code '%'} escapes, as
|
||||||
|
* described in the class header.
|
||||||
|
* @param rightPrompt The right prompt
|
||||||
|
* This is a template, with optional {@code '%'} escapes, as
|
||||||
|
* described in the class header.
|
||||||
|
* @param maskingCallback The {@link MaskingCallback} to use when displaying lines and adding them to the line {@link History}
|
||||||
|
* @param buffer The default value presented to the user to edit, may be null.
|
||||||
|
* @return A line that is read from the terminal, can never be null.
|
||||||
|
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
|
||||||
|
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
|
||||||
|
* @throws java.io.IOError in case of other i/o errors
|
||||||
|
*/
|
||||||
|
String readLine(String prompt, String rightPrompt, MaskingCallback maskingCallback, String buffer)
|
||||||
|
throws UserInterruptException, EndOfFileException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a line above the prompt and redraw everything.
|
||||||
|
* If the LineReader is not actually reading a line, the string will simply be printed to the terminal.
|
||||||
|
*
|
||||||
|
* @param str the string to print
|
||||||
|
* @see #printAbove(AttributedString)
|
||||||
|
*/
|
||||||
|
void printAbove(String str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a string before the prompt and redraw everything.
|
||||||
|
* If the LineReader is not actually reading a line, the string will simply be printed to the terminal.
|
||||||
|
*
|
||||||
|
* @param str the string to print
|
||||||
|
* @see #printAbove(String)
|
||||||
|
*/
|
||||||
|
void printAbove(AttributedString str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a thread is currently in a <code>readLine()</code> call.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if there is an ongoing <code>readLine()</code> call.
|
||||||
|
*/
|
||||||
|
boolean isReading();
|
||||||
|
|
||||||
|
LineReader variable(String name, Object value);
|
||||||
|
|
||||||
|
LineReader option(Option option, boolean value);
|
||||||
|
|
||||||
|
void callWidget(String name);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Chainable setters
|
||||||
|
//
|
||||||
|
|
||||||
|
Map<String, Object> getVariables();
|
||||||
|
|
||||||
|
Object getVariable(String name);
|
||||||
|
|
||||||
|
void setVariable(String name, Object value);
|
||||||
|
|
||||||
|
boolean isSet(Option option);
|
||||||
|
|
||||||
|
void setOpt(Option option);
|
||||||
|
|
||||||
|
void unsetOpt(Option option);
|
||||||
|
|
||||||
|
Terminal getTerminal();
|
||||||
|
|
||||||
|
Map<String, Widget> getWidgets();
|
||||||
|
|
||||||
|
Map<String, Widget> getBuiltinWidgets();
|
||||||
|
|
||||||
|
Buffer getBuffer();
|
||||||
|
|
||||||
|
String getAppName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push back a key sequence that will be later consumed by the line reader.
|
||||||
|
* This method can be used after reading the cursor position using
|
||||||
|
* {@link Terminal#getCursorPosition(IntConsumer)}.
|
||||||
|
*
|
||||||
|
* @param macro the key sequence to push back
|
||||||
|
* @see Terminal#getCursorPosition(IntConsumer)
|
||||||
|
* @see #readMouseEvent()
|
||||||
|
*/
|
||||||
|
void runMacro(String macro);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a mouse event when the {@link org.jline.utils.InfoCmp.Capability#key_mouse} sequence
|
||||||
|
* has just been read on the input stream.
|
||||||
|
* Compared to {@link Terminal#readMouseEvent()}, this method takes into account keys
|
||||||
|
* that have been pushed back using {@link #runMacro(String)}.
|
||||||
|
*
|
||||||
|
* @return the mouse event
|
||||||
|
* @see #runMacro(String)
|
||||||
|
* @see Terminal#getCursorPosition(IntConsumer)
|
||||||
|
*/
|
||||||
|
MouseEvent readMouseEvent();
|
||||||
|
|
||||||
|
History getHistory();
|
||||||
|
|
||||||
|
Parser getParser();
|
||||||
|
|
||||||
|
Highlighter getHighlighter();
|
||||||
|
|
||||||
|
Expander getExpander();
|
||||||
|
|
||||||
|
Map<String, KeyMap<Binding>> getKeyMaps();
|
||||||
|
|
||||||
|
String getKeyMap();
|
||||||
|
|
||||||
|
boolean setKeyMap(String name);
|
||||||
|
|
||||||
|
KeyMap<Binding> getKeys();
|
||||||
|
|
||||||
|
ParsedLine getParsedLine();
|
||||||
|
|
||||||
|
String getSearchTerm();
|
||||||
|
|
||||||
|
RegionType getRegionActive();
|
||||||
|
|
||||||
|
int getRegionMark();
|
||||||
|
|
||||||
|
void addCommandsInBuffer(Collection<String> commands);
|
||||||
|
|
||||||
|
void editAndAddInBuffer(File file) throws Exception;
|
||||||
|
|
||||||
|
String getLastBinding();
|
||||||
|
|
||||||
|
String getTailTip();
|
||||||
|
|
||||||
|
void setTailTip(String tailTip);
|
||||||
|
|
||||||
|
SuggestionType getAutosuggestion();
|
||||||
|
|
||||||
|
void setAutosuggestion(SuggestionType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear any internal buffers.
|
||||||
|
*/
|
||||||
|
void zeroOut();
|
||||||
|
|
||||||
|
enum Option {
|
||||||
|
COMPLETE_IN_WORD,
|
||||||
|
/**
|
||||||
|
* use camel case completion matcher
|
||||||
|
*/
|
||||||
|
COMPLETE_MATCHER_CAMELCASE,
|
||||||
|
/**
|
||||||
|
* use type completion matcher
|
||||||
|
*/
|
||||||
|
COMPLETE_MATCHER_TYPO(true),
|
||||||
|
DISABLE_EVENT_EXPANSION,
|
||||||
|
HISTORY_VERIFY,
|
||||||
|
HISTORY_IGNORE_SPACE(true),
|
||||||
|
HISTORY_IGNORE_DUPS(true),
|
||||||
|
HISTORY_REDUCE_BLANKS(true),
|
||||||
|
HISTORY_BEEP(true),
|
||||||
|
HISTORY_INCREMENTAL(true),
|
||||||
|
HISTORY_TIMESTAMPED(true),
|
||||||
|
/**
|
||||||
|
* when displaying candidates, group them by {@link Candidate#group()}
|
||||||
|
*/
|
||||||
|
AUTO_GROUP(true),
|
||||||
|
AUTO_MENU(true),
|
||||||
|
AUTO_LIST(true),
|
||||||
|
/**
|
||||||
|
* list candidates below the field to be completed
|
||||||
|
*/
|
||||||
|
AUTO_MENU_LIST,
|
||||||
|
RECOGNIZE_EXACT,
|
||||||
|
/**
|
||||||
|
* display group name before each group (else display all group names first)
|
||||||
|
*/
|
||||||
|
GROUP(true),
|
||||||
|
/**
|
||||||
|
* when double tab to select candidate keep candidates grouped (else loose grouping)
|
||||||
|
*/
|
||||||
|
GROUP_PERSIST,
|
||||||
|
/**
|
||||||
|
* if completion is case insensitive or not
|
||||||
|
*/
|
||||||
|
CASE_INSENSITIVE,
|
||||||
|
LIST_AMBIGUOUS,
|
||||||
|
LIST_PACKED,
|
||||||
|
LIST_ROWS_FIRST,
|
||||||
|
GLOB_COMPLETE,
|
||||||
|
MENU_COMPLETE,
|
||||||
|
/**
|
||||||
|
* if set and not at start of line before prompt, move to new line
|
||||||
|
*/
|
||||||
|
AUTO_FRESH_LINE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After writing into the rightmost column, do we immediately
|
||||||
|
* move to the next line (the default)? Or do we wait until
|
||||||
|
* the next character.
|
||||||
|
* If set, an input line that is exactly {@code N*columns} wide will
|
||||||
|
* use {@code N} screen lines; otherwise it will use {@code N+1} lines.
|
||||||
|
* When the cursor position is the right margin of the last line
|
||||||
|
* (i.e. after {@code N*columns} normal characters), if this option
|
||||||
|
* it set, the cursor will be remain on the last line (line {@code N-1},
|
||||||
|
* zero-origin); if unset the cursor will be on the empty next line.
|
||||||
|
* Regardless, for all except the last screen line if the cursor is at
|
||||||
|
* the right margin, it will be shown at the start of the next line.
|
||||||
|
*/
|
||||||
|
DELAY_LINE_WRAP,
|
||||||
|
AUTO_PARAM_SLASH(true),
|
||||||
|
AUTO_REMOVE_SLASH(true),
|
||||||
|
/**
|
||||||
|
* FileNameCompleter: Use '/' character as a file directory separator
|
||||||
|
*/
|
||||||
|
USE_FORWARD_SLASH,
|
||||||
|
/**
|
||||||
|
* When hitting the <code><tab></code> key at the beginning of the line, insert a tabulation
|
||||||
|
* instead of completing. This is mainly useful when {@link #BRACKETED_PASTE} is
|
||||||
|
* disabled, so that copy/paste of indented text does not trigger completion.
|
||||||
|
*/
|
||||||
|
INSERT_TAB,
|
||||||
|
MOUSE,
|
||||||
|
DISABLE_HIGHLIGHTER,
|
||||||
|
BRACKETED_PASTE(true),
|
||||||
|
/**
|
||||||
|
* Instead of printing a new line when the line is read, the entire line
|
||||||
|
* (including the prompt) will be erased, thereby leaving the screen as it
|
||||||
|
* was before the readLine call.
|
||||||
|
*/
|
||||||
|
ERASE_LINE_ON_FINISH,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if history search is fully case insensitive
|
||||||
|
*/
|
||||||
|
CASE_INSENSITIVE_SEARCH,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatic insertion of closing bracket
|
||||||
|
*/
|
||||||
|
INSERT_BRACKET,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show command options tab completion candidates for zero length word
|
||||||
|
*/
|
||||||
|
EMPTY_WORD_OPTIONS(true),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the undo feature
|
||||||
|
*/
|
||||||
|
DISABLE_UNDO;
|
||||||
|
|
||||||
|
private final boolean def;
|
||||||
|
|
||||||
|
Option() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Option(boolean def) {
|
||||||
|
this.def = def;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isSet(Map<Option, Boolean> options) {
|
||||||
|
Boolean b = options.get(this);
|
||||||
|
return b != null ? b : this.isDef();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDef() {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RegionType {
|
||||||
|
NONE,
|
||||||
|
CHAR,
|
||||||
|
LINE,
|
||||||
|
PASTE
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SuggestionType {
|
||||||
|
/**
|
||||||
|
* As you type command line suggestions are disabled.
|
||||||
|
*/
|
||||||
|
NONE,
|
||||||
|
/**
|
||||||
|
* Prepare command line suggestions using command history.
|
||||||
|
* Requires an additional widgets implementation.
|
||||||
|
*/
|
||||||
|
HISTORY,
|
||||||
|
/**
|
||||||
|
* Prepare command line suggestions using command completer data.
|
||||||
|
*/
|
||||||
|
COMPLETER,
|
||||||
|
/**
|
||||||
|
* Prepare command line suggestions using command completer data and/or command positional argument descriptions.
|
||||||
|
* Requires an additional widgets implementation.
|
||||||
|
*/
|
||||||
|
TAIL_TIP
|
||||||
|
}
|
||||||
|
}
|
155
net-cli/src/main/java/org/jline/reader/LineReaderBuilder.java
Normal file
155
net-cli/src/main/java/org/jline/reader/LineReaderBuilder.java
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.io.IOError;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.jline.reader.impl.LineReaderImpl;
|
||||||
|
import org.jline.reader.impl.history.DefaultHistory;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.terminal.TerminalBuilder;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
|
||||||
|
public final class LineReaderBuilder {
|
||||||
|
|
||||||
|
Terminal terminal;
|
||||||
|
String appName;
|
||||||
|
Map<String, Object> variables = new HashMap<>();
|
||||||
|
Map<LineReader.Option, Boolean> options = new HashMap<>();
|
||||||
|
History history;
|
||||||
|
Completer completer;
|
||||||
|
History memoryHistory;
|
||||||
|
Highlighter highlighter;
|
||||||
|
Parser parser;
|
||||||
|
Expander expander;
|
||||||
|
CompletionMatcher completionMatcher;
|
||||||
|
private LineReaderBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LineReaderBuilder builder() {
|
||||||
|
return new LineReaderBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder terminal(Terminal terminal) {
|
||||||
|
this.terminal = terminal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder appName(String appName) {
|
||||||
|
this.appName = appName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder variables(Map<String, Object> variables) {
|
||||||
|
Map<String, Object> old = this.variables;
|
||||||
|
this.variables = Objects.requireNonNull(variables);
|
||||||
|
this.variables.putAll(old);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder variable(String name, Object value) {
|
||||||
|
this.variables.put(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder option(LineReader.Option option, boolean value) {
|
||||||
|
this.options.put(option, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder history(History history) {
|
||||||
|
this.history = history;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder completer(Completer completer) {
|
||||||
|
this.completer = completer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder highlighter(Highlighter highlighter) {
|
||||||
|
this.highlighter = highlighter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder parser(Parser parser) {
|
||||||
|
if (parser != null) {
|
||||||
|
try {
|
||||||
|
if (!Boolean.getBoolean(LineReader.PROP_SUPPORT_PARSEDLINE)
|
||||||
|
&& !(parser.parse("", 0) instanceof CompletingParsedLine)) {
|
||||||
|
Log.warn("The Parser of class " + parser.getClass().getName()
|
||||||
|
+ " does not support the CompletingParsedLine interface. "
|
||||||
|
+ "Completion with escaped or quoted words won't work correctly.");
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.parser = parser;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder expander(Expander expander) {
|
||||||
|
this.expander = expander;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReaderBuilder completionMatcher(CompletionMatcher completionMatcher) {
|
||||||
|
this.completionMatcher = completionMatcher;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineReader build() {
|
||||||
|
Terminal terminal = this.terminal;
|
||||||
|
if (terminal == null) {
|
||||||
|
try {
|
||||||
|
terminal = TerminalBuilder.terminal();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String appName = this.appName;
|
||||||
|
if (null == appName) {
|
||||||
|
appName = terminal.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
LineReaderImpl reader = new LineReaderImpl(terminal, appName, variables);
|
||||||
|
if (history != null) {
|
||||||
|
reader.setHistory(history);
|
||||||
|
} else {
|
||||||
|
if (memoryHistory == null) {
|
||||||
|
memoryHistory = new DefaultHistory();
|
||||||
|
}
|
||||||
|
reader.setHistory(memoryHistory);
|
||||||
|
}
|
||||||
|
if (completer != null) {
|
||||||
|
reader.setCompleter(completer);
|
||||||
|
}
|
||||||
|
if (highlighter != null) {
|
||||||
|
reader.setHighlighter(highlighter);
|
||||||
|
}
|
||||||
|
if (parser != null) {
|
||||||
|
reader.setParser(parser);
|
||||||
|
}
|
||||||
|
if (expander != null) {
|
||||||
|
reader.setExpander(expander);
|
||||||
|
}
|
||||||
|
if (completionMatcher != null) {
|
||||||
|
reader.setCompletionMatcher(completionMatcher);
|
||||||
|
}
|
||||||
|
for (Map.Entry<LineReader.Option, Boolean> e : options.entrySet()) {
|
||||||
|
reader.option(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
}
|
40
net-cli/src/main/java/org/jline/reader/Macro.java
Normal file
40
net-cli/src/main/java/org/jline/reader/Macro.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
public class Macro implements Binding {
|
||||||
|
|
||||||
|
private final String sequence;
|
||||||
|
|
||||||
|
public Macro(String sequence) {
|
||||||
|
this.sequence = sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSequence() {
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Macro macro = (Macro) o;
|
||||||
|
return sequence.equals(macro.sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return sequence.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Macro[" + sequence + ']';
|
||||||
|
}
|
||||||
|
}
|
34
net-cli/src/main/java/org/jline/reader/MaskingCallback.java
Normal file
34
net-cli/src/main/java/org/jline/reader/MaskingCallback.java
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used to mask parts of the line
|
||||||
|
*/
|
||||||
|
public interface MaskingCallback {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the line before it is displayed so that
|
||||||
|
* some parts can be hidden.
|
||||||
|
*
|
||||||
|
* @param line the current line being edited
|
||||||
|
* @return the modified line to display
|
||||||
|
*/
|
||||||
|
String display(String line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the line before storing in the history.
|
||||||
|
* If the return value is empty or null, it will not be saved
|
||||||
|
* in the history.
|
||||||
|
*
|
||||||
|
* @param line the line to be added to history
|
||||||
|
* @return the modified line
|
||||||
|
*/
|
||||||
|
String history(String line);
|
||||||
|
}
|
67
net-cli/src/main/java/org/jline/reader/ParsedLine.java
Normal file
67
net-cli/src/main/java/org/jline/reader/ParsedLine.java
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>ParsedLine</code> objects are returned by the {@link Parser}
|
||||||
|
* during completion or when accepting the line.
|
||||||
|
* <p>
|
||||||
|
* The instances should implement the {@link CompletingParsedLine}
|
||||||
|
* interface so that escape chars and quotes can be correctly handled.
|
||||||
|
*
|
||||||
|
* @see Parser
|
||||||
|
* @see CompletingParsedLine
|
||||||
|
*/
|
||||||
|
public interface ParsedLine {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current word being completed.
|
||||||
|
* If the cursor is after the last word, an empty string is returned.
|
||||||
|
*
|
||||||
|
* @return the word being completed or an empty string
|
||||||
|
*/
|
||||||
|
String word();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cursor position within the current word.
|
||||||
|
*
|
||||||
|
* @return the cursor position within the current word
|
||||||
|
*/
|
||||||
|
int wordCursor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index of the current word in the list of words.
|
||||||
|
*
|
||||||
|
* @return the index of the current word in the list of words
|
||||||
|
*/
|
||||||
|
int wordIndex();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of words.
|
||||||
|
*
|
||||||
|
* @return the list of words
|
||||||
|
*/
|
||||||
|
List<String> words();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unparsed line.
|
||||||
|
*
|
||||||
|
* @return the unparsed line
|
||||||
|
*/
|
||||||
|
String line();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cursor position within the line.
|
||||||
|
*
|
||||||
|
* @return the cursor position within the line
|
||||||
|
*/
|
||||||
|
int cursor();
|
||||||
|
}
|
90
net-cli/src/main/java/org/jline/reader/Parser.java
Normal file
90
net-cli/src/main/java/org/jline/reader/Parser.java
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public interface Parser {
|
||||||
|
String REGEX_VARIABLE = "[a-zA-Z_]+[a-zA-Z0-9_-]*";
|
||||||
|
String REGEX_COMMAND = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*";
|
||||||
|
|
||||||
|
ParsedLine parse(String line, int cursor, ParseContext context) throws SyntaxError;
|
||||||
|
|
||||||
|
default ParsedLine parse(String line, int cursor) throws SyntaxError {
|
||||||
|
return parse(line, cursor, ParseContext.UNSPECIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isEscapeChar(char ch) {
|
||||||
|
return ch == '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean validCommandName(String name) {
|
||||||
|
return name != null && name.matches(REGEX_COMMAND);
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean validVariableName(String name) {
|
||||||
|
return name != null && name.matches(REGEX_VARIABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
default String getCommand(final String line) {
|
||||||
|
String out;
|
||||||
|
Pattern patternCommand = Pattern.compile("^\\s*" + REGEX_VARIABLE + "=(" + REGEX_COMMAND + ")(\\s+|$)");
|
||||||
|
Matcher matcher = patternCommand.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
out = matcher.group(1);
|
||||||
|
} else {
|
||||||
|
out = line.trim().split("\\s+")[0];
|
||||||
|
if (!out.matches(REGEX_COMMAND)) {
|
||||||
|
out = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
default String getVariable(final String line) {
|
||||||
|
String out = null;
|
||||||
|
Pattern patternCommand = Pattern.compile("^\\s*(" + REGEX_VARIABLE + ")\\s*=[^=~].*");
|
||||||
|
Matcher matcher = patternCommand.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
out = matcher.group(1);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ParseContext {
|
||||||
|
UNSPECIFIED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try a real "final" parse.
|
||||||
|
* May throw EOFError in which case we have incomplete input.
|
||||||
|
*/
|
||||||
|
ACCEPT_LINE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsed words will have all characters present in input line
|
||||||
|
* including quotes and escape chars.
|
||||||
|
* We should tolerate and ignore errors.
|
||||||
|
*/
|
||||||
|
SPLIT_LINE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse to find completions (typically after a Tab).
|
||||||
|
* We should tolerate and ignore errors.
|
||||||
|
*/
|
||||||
|
COMPLETE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when we need to update the secondary prompts.
|
||||||
|
* Specifically, when we need the 'missing' field from EOFError,
|
||||||
|
* which is used by a "%M" in a prompt pattern.
|
||||||
|
*/
|
||||||
|
SECONDARY_PROMPT
|
||||||
|
}
|
||||||
|
}
|
41
net-cli/src/main/java/org/jline/reader/PrintAboveWriter.java
Normal file
41
net-cli/src/main/java/org/jline/reader/PrintAboveWriter.java
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects a {@link Writer} to a {@link LineReader}'s {@link LineReader#printAbove(String)} method,
|
||||||
|
* which draws output above the current prompt / input line.
|
||||||
|
*
|
||||||
|
* <p>Example:</p>
|
||||||
|
* <pre>
|
||||||
|
* LineReader reader = LineReaderBuilder.builder().terminal(terminal).parser(parser).build();
|
||||||
|
* PrintAboveWriter printAbove = new PrintAboveWriter(reader);
|
||||||
|
* printAbove.write(new char[] { 'h', 'i', '!', '\n'});
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class PrintAboveWriter extends StringWriter {
|
||||||
|
private final LineReader reader;
|
||||||
|
|
||||||
|
public PrintAboveWriter(LineReader reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
StringBuffer buffer = getBuffer();
|
||||||
|
int lastNewline = buffer.lastIndexOf("\n");
|
||||||
|
if (lastNewline >= 0) {
|
||||||
|
reader.printAbove(buffer.substring(0, lastNewline + 1));
|
||||||
|
buffer.delete(0, lastNewline + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
net-cli/src/main/java/org/jline/reader/Reference.java
Normal file
43
net-cli/src/main/java/org/jline/reader/Reference.java
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to a {@link Widget}.
|
||||||
|
*/
|
||||||
|
public class Reference implements Binding {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public Reference(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Reference func = (Reference) o;
|
||||||
|
return name.equals(func.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Reference[" + name + ']';
|
||||||
|
}
|
||||||
|
}
|
31
net-cli/src/main/java/org/jline/reader/SyntaxError.java
Normal file
31
net-cli/src/main/java/org/jline/reader/SyntaxError.java
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
public class SyntaxError extends RuntimeException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final int line;
|
||||||
|
private final int column;
|
||||||
|
|
||||||
|
public SyntaxError(int line, int column, String message) {
|
||||||
|
super(message);
|
||||||
|
this.line = line;
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int column() {
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int line() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown by {@link LineReader#readLine} when
|
||||||
|
* user interrupt handling is enabled and the user types the
|
||||||
|
* interrupt character (ctrl-C). The partially entered line is
|
||||||
|
* available via the {@link #getPartialLine()} method.
|
||||||
|
*/
|
||||||
|
public class UserInterruptException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = 6172232572140736750L;
|
||||||
|
|
||||||
|
private final String partialLine;
|
||||||
|
|
||||||
|
public UserInterruptException(String partialLine) {
|
||||||
|
this.partialLine = partialLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the partially entered line when ctrl-C was pressed
|
||||||
|
*/
|
||||||
|
public String getPartialLine() {
|
||||||
|
return partialLine;
|
||||||
|
}
|
||||||
|
}
|
18
net-cli/src/main/java/org/jline/reader/Widget.java
Normal file
18
net-cli/src/main/java/org/jline/reader/Widget.java
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Widget extends Binding {
|
||||||
|
|
||||||
|
boolean apply();
|
||||||
|
}
|
374
net-cli/src/main/java/org/jline/reader/impl/BufferImpl.java
Normal file
374
net-cli/src/main/java/org/jline/reader/impl/BufferImpl.java
Normal file
|
@ -0,0 +1,374 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2017, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.jline.reader.Buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A holder for a {@link StringBuilder} that also contains the current cursor position.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||||
|
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public class BufferImpl implements Buffer {
|
||||||
|
private int cursor = 0;
|
||||||
|
private int cursorCol = -1;
|
||||||
|
private int[] buffer;
|
||||||
|
private int g0;
|
||||||
|
private int g1;
|
||||||
|
|
||||||
|
public BufferImpl() {
|
||||||
|
this(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferImpl(int size) {
|
||||||
|
buffer = new int[size];
|
||||||
|
g0 = 0;
|
||||||
|
g1 = buffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferImpl(BufferImpl buffer) {
|
||||||
|
this.cursor = buffer.cursor;
|
||||||
|
this.cursorCol = buffer.cursorCol;
|
||||||
|
this.buffer = buffer.buffer.clone();
|
||||||
|
this.g0 = buffer.g0;
|
||||||
|
this.g1 = buffer.g1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferImpl copy() {
|
||||||
|
return new BufferImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int cursor() {
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int length() {
|
||||||
|
return buffer.length - (g1 - g0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean currChar(int ch) {
|
||||||
|
if (cursor == length()) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
buffer[adjust(cursor)] = ch;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int currChar() {
|
||||||
|
if (cursor == length()) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return atChar(cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int prevChar() {
|
||||||
|
if (cursor <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return atChar(cursor - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nextChar() {
|
||||||
|
if (cursor >= length() - 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return atChar(cursor + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int atChar(int i) {
|
||||||
|
if (i < 0 || i >= length()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return buffer[adjust(i)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private int adjust(int i) {
|
||||||
|
return (i >= g0) ? i + g1 - g0 : i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the specific character into the buffer, setting the cursor position
|
||||||
|
* ahead one.
|
||||||
|
*
|
||||||
|
* @param c the character to insert
|
||||||
|
*/
|
||||||
|
public void write(int c) {
|
||||||
|
write(new int[]{c});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the specific character into the buffer, setting the cursor position
|
||||||
|
* ahead one. The text may overwrite or insert based on the current setting
|
||||||
|
* of {@code overTyping}.
|
||||||
|
*
|
||||||
|
* @param c the character to insert
|
||||||
|
*/
|
||||||
|
public void write(int c, boolean overTyping) {
|
||||||
|
if (overTyping) {
|
||||||
|
delete(1);
|
||||||
|
}
|
||||||
|
write(new int[]{c});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert the specified chars into the buffer, setting the cursor to the end of the insertion point.
|
||||||
|
*/
|
||||||
|
public void write(CharSequence str) {
|
||||||
|
Objects.requireNonNull(str);
|
||||||
|
write(str.codePoints().toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(CharSequence str, boolean overTyping) {
|
||||||
|
Objects.requireNonNull(str);
|
||||||
|
int[] ucps = str.codePoints().toArray();
|
||||||
|
if (overTyping) {
|
||||||
|
delete(ucps.length);
|
||||||
|
}
|
||||||
|
write(ucps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(int[] ucps) {
|
||||||
|
moveGapToCursor();
|
||||||
|
int len = length() + ucps.length;
|
||||||
|
int sz = buffer.length;
|
||||||
|
if (sz < len) {
|
||||||
|
while (sz < len) {
|
||||||
|
sz *= 2;
|
||||||
|
}
|
||||||
|
int[] nb = new int[sz];
|
||||||
|
System.arraycopy(buffer, 0, nb, 0, g0);
|
||||||
|
System.arraycopy(buffer, g1, nb, g1 + sz - buffer.length, buffer.length - g1);
|
||||||
|
g1 += sz - buffer.length;
|
||||||
|
buffer = nb;
|
||||||
|
}
|
||||||
|
System.arraycopy(ucps, 0, buffer, cursor, ucps.length);
|
||||||
|
g0 += ucps.length;
|
||||||
|
cursor += ucps.length;
|
||||||
|
cursorCol = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean clear() {
|
||||||
|
if (length() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g0 = 0;
|
||||||
|
g1 = buffer.length;
|
||||||
|
cursor = 0;
|
||||||
|
cursorCol = -1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String substring(int start) {
|
||||||
|
return substring(start, length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String substring(int start, int end) {
|
||||||
|
if (start >= end || start < 0 || end > length()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (end <= g0) {
|
||||||
|
return new String(buffer, start, end - start);
|
||||||
|
} else if (start > g0) {
|
||||||
|
return new String(buffer, g1 - g0 + start, end - start);
|
||||||
|
} else {
|
||||||
|
int[] b = buffer.clone();
|
||||||
|
System.arraycopy(b, g1, b, g0, b.length - g1);
|
||||||
|
return new String(b, start, end - start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String upToCursor() {
|
||||||
|
return substring(0, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor position to the specified absolute index.
|
||||||
|
*/
|
||||||
|
public boolean cursor(int position) {
|
||||||
|
if (position == cursor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return move(position - cursor) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor <i>where</i> characters.
|
||||||
|
*
|
||||||
|
* @param num If less than 0, move abs(<i>where</i>) to the left, otherwise move <i>where</i> to the right.
|
||||||
|
* @return The number of spaces we moved
|
||||||
|
*/
|
||||||
|
public int move(final int num) {
|
||||||
|
int where = num;
|
||||||
|
|
||||||
|
if ((cursor == 0) && (where <= 0)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((cursor == length()) && (where >= 0)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((cursor + where) < 0) {
|
||||||
|
where = -cursor;
|
||||||
|
} else if ((cursor + where) > length()) {
|
||||||
|
where = length() - cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor += where;
|
||||||
|
cursorCol = -1;
|
||||||
|
|
||||||
|
return where;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean up() {
|
||||||
|
int col = getCursorCol();
|
||||||
|
int pnl = cursor - 1;
|
||||||
|
while (pnl >= 0 && atChar(pnl) != '\n') {
|
||||||
|
pnl--;
|
||||||
|
}
|
||||||
|
if (pnl < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int ppnl = pnl - 1;
|
||||||
|
while (ppnl >= 0 && atChar(ppnl) != '\n') {
|
||||||
|
ppnl--;
|
||||||
|
}
|
||||||
|
cursor = Math.min(ppnl + col + 1, pnl);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean down() {
|
||||||
|
int col = getCursorCol();
|
||||||
|
int nnl = cursor;
|
||||||
|
while (nnl < length() && atChar(nnl) != '\n') {
|
||||||
|
nnl++;
|
||||||
|
}
|
||||||
|
if (nnl >= length()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int nnnl = nnl + 1;
|
||||||
|
while (nnnl < length() && atChar(nnnl) != '\n') {
|
||||||
|
nnnl++;
|
||||||
|
}
|
||||||
|
cursor = Math.min(nnl + col + 1, nnnl);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean moveXY(int dx, int dy) {
|
||||||
|
int col = 0;
|
||||||
|
while (prevChar() != '\n' && move(-1) == -1) {
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
cursorCol = 0;
|
||||||
|
while (dy < 0) {
|
||||||
|
up();
|
||||||
|
dy++;
|
||||||
|
}
|
||||||
|
while (dy > 0) {
|
||||||
|
down();
|
||||||
|
dy--;
|
||||||
|
}
|
||||||
|
col = Math.max(col + dx, 0);
|
||||||
|
for (int i = 0; i < col; i++) {
|
||||||
|
if (move(1) != 1 || currChar() == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursorCol = col;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCursorCol() {
|
||||||
|
if (cursorCol < 0) {
|
||||||
|
cursorCol = 0;
|
||||||
|
int pnl = cursor - 1;
|
||||||
|
while (pnl >= 0 && atChar(pnl) != '\n') {
|
||||||
|
pnl--;
|
||||||
|
}
|
||||||
|
cursorCol = cursor - pnl - 1;
|
||||||
|
}
|
||||||
|
return cursorCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue <em>num</em> backspaces.
|
||||||
|
*
|
||||||
|
* @return the number of characters backed up
|
||||||
|
*/
|
||||||
|
public int backspace(final int num) {
|
||||||
|
int count = Math.max(Math.min(cursor, num), 0);
|
||||||
|
moveGapToCursor();
|
||||||
|
cursor -= count;
|
||||||
|
g0 -= count;
|
||||||
|
cursorCol = -1;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue a backspace.
|
||||||
|
*
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
public boolean backspace() {
|
||||||
|
return backspace(1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int delete(int num) {
|
||||||
|
int count = Math.max(Math.min(length() - cursor, num), 0);
|
||||||
|
moveGapToCursor();
|
||||||
|
g1 += count;
|
||||||
|
cursorCol = -1;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean delete() {
|
||||||
|
return delete(1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return substring(0, length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyFrom(Buffer buf) {
|
||||||
|
if (!(buf instanceof BufferImpl that)) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
this.g0 = that.g0;
|
||||||
|
this.g1 = that.g1;
|
||||||
|
this.buffer = that.buffer.clone();
|
||||||
|
this.cursor = that.cursor;
|
||||||
|
this.cursorCol = that.cursorCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveGapToCursor() {
|
||||||
|
if (cursor < g0) {
|
||||||
|
int l = g0 - cursor;
|
||||||
|
System.arraycopy(buffer, cursor, buffer, g1 - l, l);
|
||||||
|
g0 -= l;
|
||||||
|
g1 -= l;
|
||||||
|
} else if (cursor > g0) {
|
||||||
|
int l = cursor - g0;
|
||||||
|
System.arraycopy(buffer, g1, buffer, g0, l);
|
||||||
|
g0 += l;
|
||||||
|
g1 += l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void zeroOut() {
|
||||||
|
Arrays.fill(buffer, 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.CompletingParsedLine;
|
||||||
|
import org.jline.reader.CompletionMatcher;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
|
||||||
|
public class CompletionMatcherImpl implements CompletionMatcher {
|
||||||
|
protected Predicate<String> exact;
|
||||||
|
protected List<Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>> matchers;
|
||||||
|
private Map<String, List<Candidate>> matching;
|
||||||
|
private boolean caseInsensitive;
|
||||||
|
|
||||||
|
public CompletionMatcherImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void reset(boolean caseInsensitive) {
|
||||||
|
this.caseInsensitive = caseInsensitive;
|
||||||
|
exact = s -> false;
|
||||||
|
matchers = new ArrayList<>();
|
||||||
|
matching = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(
|
||||||
|
Map<LineReader.Option, Boolean> options,
|
||||||
|
boolean prefix,
|
||||||
|
CompletingParsedLine line,
|
||||||
|
boolean caseInsensitive,
|
||||||
|
int errors,
|
||||||
|
String originalGroupName) {
|
||||||
|
reset(caseInsensitive);
|
||||||
|
defaultMatchers(options, prefix, line, caseInsensitive, errors, originalGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Candidate> matches(List<Candidate> candidates) {
|
||||||
|
matching = Collections.emptyMap();
|
||||||
|
Map<String, List<Candidate>> sortedCandidates = sort(candidates);
|
||||||
|
for (Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>> matcher : matchers) {
|
||||||
|
matching = matcher.apply(sortedCandidates);
|
||||||
|
if (!matching.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !matching.isEmpty()
|
||||||
|
? matching.entrySet().stream()
|
||||||
|
.flatMap(e -> e.getValue().stream())
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
: new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Candidate exactMatch() {
|
||||||
|
if (matching == null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
return matching.values().stream()
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.filter(Candidate::complete)
|
||||||
|
.filter(c -> exact.test(c.value()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommonPrefix() {
|
||||||
|
if (matching == null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
String commonPrefix = null;
|
||||||
|
for (String key : matching.keySet()) {
|
||||||
|
commonPrefix = commonPrefix == null ? key : getCommonStart(commonPrefix, key, caseInsensitive);
|
||||||
|
}
|
||||||
|
return commonPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default JLine matchers
|
||||||
|
*/
|
||||||
|
protected void defaultMatchers(
|
||||||
|
Map<LineReader.Option, Boolean> options,
|
||||||
|
boolean prefix,
|
||||||
|
CompletingParsedLine line,
|
||||||
|
boolean caseInsensitive,
|
||||||
|
int errors,
|
||||||
|
String originalGroupName) {
|
||||||
|
// Find matchers
|
||||||
|
// TODO: glob completion
|
||||||
|
String wd = line.word();
|
||||||
|
String wdi = caseInsensitive ? wd.toLowerCase() : wd;
|
||||||
|
String wp = wdi.substring(0, line.wordCursor());
|
||||||
|
if (prefix) {
|
||||||
|
matchers = new ArrayList<>(Arrays.asList(
|
||||||
|
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wp)),
|
||||||
|
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wp))));
|
||||||
|
if (LineReader.Option.COMPLETE_MATCHER_TYPO.isSet(options)) {
|
||||||
|
matchers.add(typoMatcher(wp, errors, caseInsensitive, originalGroupName));
|
||||||
|
}
|
||||||
|
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wp) : s.equals(wp);
|
||||||
|
} else if (!LineReader.Option.EMPTY_WORD_OPTIONS.isSet(options) && wd.length() == 0) {
|
||||||
|
matchers = new ArrayList<>(Collections.singletonList(simpleMatcher(s -> !s.startsWith("-"))));
|
||||||
|
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
|
||||||
|
} else {
|
||||||
|
if (LineReader.Option.COMPLETE_IN_WORD.isSet(options)) {
|
||||||
|
String ws = wdi.substring(line.wordCursor());
|
||||||
|
Pattern p1 = Pattern.compile(Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
|
||||||
|
Pattern p2 = Pattern.compile(".*" + Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
|
||||||
|
matchers = new ArrayList<>(Arrays.asList(
|
||||||
|
simpleMatcher(s -> p1.matcher(caseInsensitive ? s.toLowerCase() : s)
|
||||||
|
.matches()),
|
||||||
|
simpleMatcher(s -> p2.matcher(caseInsensitive ? s.toLowerCase() : s)
|
||||||
|
.matches())));
|
||||||
|
} else {
|
||||||
|
matchers = new ArrayList<>(Arrays.asList(
|
||||||
|
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wdi)),
|
||||||
|
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wdi))));
|
||||||
|
}
|
||||||
|
if (LineReader.Option.COMPLETE_MATCHER_CAMELCASE.isSet(options)) {
|
||||||
|
matchers.add(simpleMatcher(s -> camelMatch(wd, 0, s, 0)));
|
||||||
|
}
|
||||||
|
if (LineReader.Option.COMPLETE_MATCHER_TYPO.isSet(options)) {
|
||||||
|
matchers.add(typoMatcher(wdi, errors, caseInsensitive, originalGroupName));
|
||||||
|
}
|
||||||
|
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>> simpleMatcher(
|
||||||
|
Predicate<String> predicate) {
|
||||||
|
return m -> m.entrySet().stream()
|
||||||
|
.filter(e -> predicate.test(e.getKey()))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>> typoMatcher(
|
||||||
|
String word, int errors, boolean caseInsensitive, String originalGroupName) {
|
||||||
|
return m -> {
|
||||||
|
Map<String, List<Candidate>> map = m.entrySet().stream()
|
||||||
|
.filter(e -> ReaderUtils.distance(
|
||||||
|
word, caseInsensitive ? e.getKey().toLowerCase() : e.getKey())
|
||||||
|
< errors)
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
if (map.size() > 1) {
|
||||||
|
map.computeIfAbsent(word, w -> new ArrayList<>())
|
||||||
|
.add(new Candidate(word, word, originalGroupName, null, null, null, false));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean camelMatch(String word, int i, String candidate, int j) {
|
||||||
|
if (word.length() <= i) {
|
||||||
|
return true;
|
||||||
|
} else if (candidate.length() <= j) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
char c = word.charAt(i);
|
||||||
|
if (c == candidate.charAt(j)) {
|
||||||
|
return camelMatch(word, i + 1, candidate, j + 1);
|
||||||
|
} else {
|
||||||
|
for (int j1 = j; j1 < candidate.length(); j1++) {
|
||||||
|
if (Character.isUpperCase(candidate.charAt(j1))) {
|
||||||
|
if (Character.toUpperCase(c) == candidate.charAt(j1)) {
|
||||||
|
if (camelMatch(word, i + 1, candidate, j1 + 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, List<Candidate>> sort(List<Candidate> candidates) {
|
||||||
|
// Build a list of sorted candidates
|
||||||
|
Map<String, List<Candidate>> sortedCandidates = new HashMap<>();
|
||||||
|
for (Candidate candidate : candidates) {
|
||||||
|
sortedCandidates
|
||||||
|
.computeIfAbsent(
|
||||||
|
AttributedString.fromAnsi(candidate.value()).toString(), s -> new ArrayList<>())
|
||||||
|
.add(candidate);
|
||||||
|
}
|
||||||
|
return sortedCandidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCommonStart(String str1, String str2, boolean caseInsensitive) {
|
||||||
|
int[] s1 = str1.codePoints().toArray();
|
||||||
|
int[] s2 = str2.codePoints().toArray();
|
||||||
|
int len = 0;
|
||||||
|
while (len < Math.min(s1.length, s2.length)) {
|
||||||
|
int ch1 = s1[len];
|
||||||
|
int ch2 = s2[len];
|
||||||
|
if (ch1 != ch2 && caseInsensitive) {
|
||||||
|
ch1 = Character.toUpperCase(ch1);
|
||||||
|
ch2 = Character.toUpperCase(ch2);
|
||||||
|
if (ch1 != ch2) {
|
||||||
|
ch1 = Character.toLowerCase(ch1);
|
||||||
|
ch2 = Character.toLowerCase(ch2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch1 != ch2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
return new String(s1, 0, len);
|
||||||
|
}
|
||||||
|
}
|
209
net-cli/src/main/java/org/jline/reader/impl/DefaultExpander.java
Normal file
209
net-cli/src/main/java/org/jline/reader/impl/DefaultExpander.java
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import org.jline.reader.Expander;
|
||||||
|
import org.jline.reader.History;
|
||||||
|
import org.jline.reader.History.Entry;
|
||||||
|
|
||||||
|
public class DefaultExpander implements Expander {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand event designator such as !!, !#, !3, etc...
|
||||||
|
* See http://www.gnu.org/software/bash/manual/html_node/Event-Designators.html
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("fallthrough")
|
||||||
|
@Override
|
||||||
|
public String expandHistory(History history, String line) {
|
||||||
|
boolean inQuote = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean escaped = false;
|
||||||
|
int unicode = 0;
|
||||||
|
for (int i = 0; i < line.length(); i++) {
|
||||||
|
char c = line.charAt(i);
|
||||||
|
if (unicode > 0) {
|
||||||
|
escaped = (--unicode >= 0);
|
||||||
|
sb.append(c);
|
||||||
|
} else if (escaped) {
|
||||||
|
if (c == 'u') {
|
||||||
|
unicode = 4;
|
||||||
|
} else {
|
||||||
|
escaped = false;
|
||||||
|
}
|
||||||
|
sb.append(c);
|
||||||
|
} else if (c == '\'') {
|
||||||
|
inQuote = !inQuote;
|
||||||
|
sb.append(c);
|
||||||
|
} else if (inQuote) {
|
||||||
|
sb.append(c);
|
||||||
|
} else {
|
||||||
|
switch (c) {
|
||||||
|
case '\\':
|
||||||
|
// any '\!' should be considered an expansion escape, so skip expansion and strip the escape
|
||||||
|
// character
|
||||||
|
// a leading '\^' should be considered an expansion escape, so skip expansion and strip the
|
||||||
|
// escape character
|
||||||
|
// otherwise, add the escape
|
||||||
|
escaped = true;
|
||||||
|
sb.append(c);
|
||||||
|
break;
|
||||||
|
case '!':
|
||||||
|
if (i + 1 < line.length()) {
|
||||||
|
c = line.charAt(++i);
|
||||||
|
boolean neg = false;
|
||||||
|
String rep = null;
|
||||||
|
int i1, idx;
|
||||||
|
switch (c) {
|
||||||
|
case '!':
|
||||||
|
if (history.size() == 0) {
|
||||||
|
throw new IllegalArgumentException("!!: event not found");
|
||||||
|
}
|
||||||
|
rep = history.get(history.index() - 1);
|
||||||
|
break;
|
||||||
|
case '#':
|
||||||
|
sb.append(sb);
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
i1 = line.indexOf('?', i + 1);
|
||||||
|
if (i1 < 0) {
|
||||||
|
i1 = line.length();
|
||||||
|
}
|
||||||
|
String sc = line.substring(i + 1, i1);
|
||||||
|
i = i1;
|
||||||
|
idx = searchBackwards(history, sc, history.index(), false);
|
||||||
|
if (idx < 0) {
|
||||||
|
throw new IllegalArgumentException("!?" + sc + ": event not found");
|
||||||
|
} else {
|
||||||
|
rep = history.get(idx);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '$':
|
||||||
|
if (history.size() == 0) {
|
||||||
|
throw new IllegalArgumentException("!$: event not found");
|
||||||
|
}
|
||||||
|
String previous =
|
||||||
|
history.get(history.index() - 1).trim();
|
||||||
|
int lastSpace = previous.lastIndexOf(' ');
|
||||||
|
if (lastSpace != -1) {
|
||||||
|
rep = previous.substring(lastSpace + 1);
|
||||||
|
} else {
|
||||||
|
rep = previous;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
sb.append('!');
|
||||||
|
sb.append(c);
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
neg = true;
|
||||||
|
i++;
|
||||||
|
// fall through
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
i1 = i;
|
||||||
|
for (; i < line.length(); i++) {
|
||||||
|
c = line.charAt(i);
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
idx = Integer.parseInt(line.substring(i1, i));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
(neg ? "!-" : "!") + line.substring(i1, i) + ": event not found");
|
||||||
|
}
|
||||||
|
if (neg && idx > 0 && idx <= history.size()) {
|
||||||
|
rep = history.get(history.index() - idx);
|
||||||
|
} else if (!neg
|
||||||
|
&& idx > history.index() - history.size()
|
||||||
|
&& idx <= history.index()) {
|
||||||
|
rep = history.get(idx - 1);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
(neg ? "!-" : "!") + line.substring(i1, i) + ": event not found");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
String ss = line.substring(i);
|
||||||
|
i = line.length();
|
||||||
|
idx = searchBackwards(history, ss, history.index(), true);
|
||||||
|
if (idx < 0) {
|
||||||
|
throw new IllegalArgumentException("!" + ss + ": event not found");
|
||||||
|
} else {
|
||||||
|
rep = history.get(idx);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (rep != null) {
|
||||||
|
sb.append(rep);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '^':
|
||||||
|
if (i == 0) {
|
||||||
|
int i1 = line.indexOf('^', i + 1);
|
||||||
|
int i2 = line.indexOf('^', i1 + 1);
|
||||||
|
if (i2 < 0) {
|
||||||
|
i2 = line.length();
|
||||||
|
}
|
||||||
|
if (i1 > 0 && i2 > 0) {
|
||||||
|
String s1 = line.substring(i + 1, i1);
|
||||||
|
String s2 = line.substring(i1 + 1, i2);
|
||||||
|
String s = history.get(history.index() - 1).replace(s1, s2);
|
||||||
|
sb.append(s);
|
||||||
|
i = i2 + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(c);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String expandVar(String word) {
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int searchBackwards(History history, String searchTerm, int startIndex, boolean startsWith) {
|
||||||
|
ListIterator<Entry> it = history.iterator(startIndex);
|
||||||
|
while (it.hasPrevious()) {
|
||||||
|
Entry e = it.previous();
|
||||||
|
if (startsWith) {
|
||||||
|
if (e.line().startsWith(searchTerm)) {
|
||||||
|
return e.index();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (e.line().contains(searchTerm)) {
|
||||||
|
return e.index();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import org.jline.reader.Highlighter;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.LineReader.RegionType;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
import org.jline.utils.AttributedStringBuilder;
|
||||||
|
import org.jline.utils.AttributedStyle;
|
||||||
|
import org.jline.utils.WCWidth;
|
||||||
|
|
||||||
|
public class DefaultHighlighter implements Highlighter {
|
||||||
|
protected Pattern errorPattern;
|
||||||
|
protected int errorIndex = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setErrorPattern(Pattern errorPattern) {
|
||||||
|
this.errorPattern = errorPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setErrorIndex(int errorIndex) {
|
||||||
|
this.errorIndex = errorIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AttributedString highlight(LineReader reader, String buffer) {
|
||||||
|
int underlineStart = -1;
|
||||||
|
int underlineEnd = -1;
|
||||||
|
int negativeStart = -1;
|
||||||
|
int negativeEnd = -1;
|
||||||
|
String search = reader.getSearchTerm();
|
||||||
|
if (search != null && search.length() > 0) {
|
||||||
|
underlineStart = buffer.indexOf(search);
|
||||||
|
if (underlineStart >= 0) {
|
||||||
|
underlineEnd = underlineStart + search.length() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reader.getRegionActive() != RegionType.NONE) {
|
||||||
|
negativeStart = reader.getRegionMark();
|
||||||
|
negativeEnd = reader.getBuffer().cursor();
|
||||||
|
if (negativeStart > negativeEnd) {
|
||||||
|
int x = negativeEnd;
|
||||||
|
negativeEnd = negativeStart;
|
||||||
|
negativeStart = x;
|
||||||
|
}
|
||||||
|
if (reader.getRegionActive() == RegionType.LINE) {
|
||||||
|
while (negativeStart > 0 && reader.getBuffer().atChar(negativeStart - 1) != '\n') {
|
||||||
|
negativeStart--;
|
||||||
|
}
|
||||||
|
while (negativeEnd < reader.getBuffer().length() - 1
|
||||||
|
&& reader.getBuffer().atChar(negativeEnd + 1) != '\n') {
|
||||||
|
negativeEnd++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AttributedStringBuilder sb = new AttributedStringBuilder();
|
||||||
|
for (int i = 0; i < buffer.length(); i++) {
|
||||||
|
if (i == underlineStart) {
|
||||||
|
sb.style(AttributedStyle::underline);
|
||||||
|
}
|
||||||
|
if (i == negativeStart) {
|
||||||
|
sb.style(AttributedStyle::inverse);
|
||||||
|
}
|
||||||
|
if (i == errorIndex) {
|
||||||
|
sb.style(AttributedStyle::inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
char c = buffer.charAt(i);
|
||||||
|
if (c == '\t' || c == '\n') {
|
||||||
|
sb.append(c);
|
||||||
|
} else if (c < 32) {
|
||||||
|
sb.style(AttributedStyle::inverseNeg)
|
||||||
|
.append('^')
|
||||||
|
.append((char) (c + '@'))
|
||||||
|
.style(AttributedStyle::inverseNeg);
|
||||||
|
} else {
|
||||||
|
int w = WCWidth.wcwidth(c);
|
||||||
|
if (w > 0) {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == underlineEnd) {
|
||||||
|
sb.style(AttributedStyle::underlineOff);
|
||||||
|
}
|
||||||
|
if (i == negativeEnd) {
|
||||||
|
sb.style(AttributedStyle::inverseOff);
|
||||||
|
}
|
||||||
|
if (i == errorIndex) {
|
||||||
|
sb.style(AttributedStyle::inverseOff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorPattern != null) {
|
||||||
|
sb.styleMatches(errorPattern, AttributedStyle.INVERSE);
|
||||||
|
}
|
||||||
|
return sb.toAttributedString();
|
||||||
|
}
|
||||||
|
}
|
811
net-cli/src/main/java/org/jline/reader/impl/DefaultParser.java
Normal file
811
net-cli/src/main/java/org/jline/reader/impl/DefaultParser.java
Normal file
|
@ -0,0 +1,811 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import org.jline.reader.CompletingParsedLine;
|
||||||
|
import org.jline.reader.EOFError;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
import org.jline.reader.Parser;
|
||||||
|
|
||||||
|
public class DefaultParser implements Parser {
|
||||||
|
|
||||||
|
private char[] quoteChars = {'\'', '"'};
|
||||||
|
private char[] escapeChars = {'\\'};
|
||||||
|
private boolean eofOnUnclosedQuote;
|
||||||
|
private boolean eofOnEscapedNewLine;
|
||||||
|
private char[] openingBrackets = null;
|
||||||
|
private char[] closingBrackets = null;
|
||||||
|
private String[] lineCommentDelims = null;
|
||||||
|
private BlockCommentDelims blockCommentDelims = null;
|
||||||
|
private String regexVariable = "[a-zA-Z_]+[a-zA-Z0-9_-]*((\\.|\\['|\\[\"|\\[)[a-zA-Z0-9_-]*(|']|\"]|]))?";
|
||||||
|
private String regexCommand = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*";
|
||||||
|
private int commandGroup = 4;
|
||||||
|
|
||||||
|
public DefaultParser lineCommentDelims(final String[] lineCommentDelims) {
|
||||||
|
this.lineCommentDelims = lineCommentDelims;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultParser blockCommentDelims(final BlockCommentDelims blockCommentDelims) {
|
||||||
|
this.blockCommentDelims = blockCommentDelims;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Chainable setters
|
||||||
|
//
|
||||||
|
|
||||||
|
public DefaultParser quoteChars(final char[] chars) {
|
||||||
|
this.quoteChars = chars;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultParser escapeChars(final char[] chars) {
|
||||||
|
this.escapeChars = chars;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultParser eofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
|
||||||
|
this.eofOnUnclosedQuote = eofOnUnclosedQuote;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultParser eofOnUnclosedBracket(Bracket... brackets) {
|
||||||
|
setEofOnUnclosedBracket(brackets);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultParser eofOnEscapedNewLine(boolean eofOnEscapedNewLine) {
|
||||||
|
this.eofOnEscapedNewLine = eofOnEscapedNewLine;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultParser regexVariable(String regexVariable) {
|
||||||
|
this.regexVariable = regexVariable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultParser regexCommand(String regexCommand) {
|
||||||
|
this.regexCommand = regexCommand;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultParser commandGroup(int commandGroup) {
|
||||||
|
this.commandGroup = commandGroup;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char[] getQuoteChars() {
|
||||||
|
return this.quoteChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuoteChars(final char[] chars) {
|
||||||
|
this.quoteChars = chars;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Java bean getters and setters
|
||||||
|
//
|
||||||
|
|
||||||
|
public char[] getEscapeChars() {
|
||||||
|
return this.escapeChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEscapeChars(final char[] chars) {
|
||||||
|
this.escapeChars = chars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getLineCommentDelims() {
|
||||||
|
return this.lineCommentDelims;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLineCommentDelims(String[] lineCommentDelims) {
|
||||||
|
this.lineCommentDelims = lineCommentDelims;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockCommentDelims getBlockCommentDelims() {
|
||||||
|
return blockCommentDelims;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockCommentDelims(BlockCommentDelims blockCommentDelims) {
|
||||||
|
this.blockCommentDelims = blockCommentDelims;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEofOnUnclosedQuote() {
|
||||||
|
return eofOnUnclosedQuote;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
|
||||||
|
this.eofOnUnclosedQuote = eofOnUnclosedQuote;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEofOnEscapedNewLine() {
|
||||||
|
return eofOnEscapedNewLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEofOnEscapedNewLine(boolean eofOnEscapedNewLine) {
|
||||||
|
this.eofOnEscapedNewLine = eofOnEscapedNewLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEofOnUnclosedBracket(Bracket... brackets) {
|
||||||
|
if (brackets == null) {
|
||||||
|
openingBrackets = null;
|
||||||
|
closingBrackets = null;
|
||||||
|
} else {
|
||||||
|
Set<Bracket> bs = new HashSet<>(Arrays.asList(brackets));
|
||||||
|
openingBrackets = new char[bs.size()];
|
||||||
|
closingBrackets = new char[bs.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (Bracket b : bs) {
|
||||||
|
switch (b) {
|
||||||
|
case ROUND:
|
||||||
|
openingBrackets[i] = '(';
|
||||||
|
closingBrackets[i] = ')';
|
||||||
|
break;
|
||||||
|
case CURLY:
|
||||||
|
openingBrackets[i] = '{';
|
||||||
|
closingBrackets[i] = '}';
|
||||||
|
break;
|
||||||
|
case SQUARE:
|
||||||
|
openingBrackets[i] = '[';
|
||||||
|
closingBrackets[i] = ']';
|
||||||
|
break;
|
||||||
|
case ANGLE:
|
||||||
|
openingBrackets[i] = '<';
|
||||||
|
closingBrackets[i] = '>';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegexVariable(String regexVariable) {
|
||||||
|
this.regexVariable = regexVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegexCommand(String regexCommand) {
|
||||||
|
this.regexCommand = regexCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommandGroup(int commandGroup) {
|
||||||
|
this.commandGroup = commandGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validCommandName(String name) {
|
||||||
|
return name != null && name.matches(regexCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validVariableName(String name) {
|
||||||
|
return name != null && regexVariable != null && name.matches(regexVariable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommand(final String line) {
|
||||||
|
String out = "";
|
||||||
|
boolean checkCommandOnly = regexVariable == null;
|
||||||
|
if (!checkCommandOnly) {
|
||||||
|
Pattern patternCommand = Pattern.compile("^\\s*" + regexVariable + "=(" + regexCommand + ")(\\s+|$)");
|
||||||
|
Matcher matcher = patternCommand.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
out = matcher.group(commandGroup);
|
||||||
|
} else {
|
||||||
|
checkCommandOnly = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (checkCommandOnly) {
|
||||||
|
out = line.trim().split("\\s+")[0];
|
||||||
|
if (!out.matches(regexCommand)) {
|
||||||
|
out = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVariable(final String line) {
|
||||||
|
String out = null;
|
||||||
|
if (regexVariable != null) {
|
||||||
|
Pattern patternCommand = Pattern.compile("^\\s*(" + regexVariable + ")\\s*=[^=~].*");
|
||||||
|
Matcher matcher = patternCommand.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
out = matcher.group(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParsedLine parse(final String line, final int cursor, ParseContext context) {
|
||||||
|
List<String> words = new LinkedList<>();
|
||||||
|
StringBuilder current = new StringBuilder();
|
||||||
|
int wordCursor = -1;
|
||||||
|
int wordIndex = -1;
|
||||||
|
int quoteStart = -1;
|
||||||
|
int rawWordCursor = -1;
|
||||||
|
int rawWordLength = -1;
|
||||||
|
int rawWordStart = 0;
|
||||||
|
BracketChecker bracketChecker = new BracketChecker(cursor);
|
||||||
|
boolean quotedWord = false;
|
||||||
|
boolean lineCommented = false;
|
||||||
|
boolean blockCommented = false;
|
||||||
|
boolean blockCommentInRightOrder = true;
|
||||||
|
final String blockCommentEnd = blockCommentDelims == null ? null : blockCommentDelims.end;
|
||||||
|
final String blockCommentStart = blockCommentDelims == null ? null : blockCommentDelims.start;
|
||||||
|
|
||||||
|
for (int i = 0; (line != null) && (i < line.length()); i++) {
|
||||||
|
// once we reach the cursor, set the
|
||||||
|
// position of the selected index
|
||||||
|
if (i == cursor) {
|
||||||
|
wordIndex = words.size();
|
||||||
|
// the position in the current argument is just the
|
||||||
|
// length of the current argument
|
||||||
|
wordCursor = current.length();
|
||||||
|
rawWordCursor = i - rawWordStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quoteStart < 0 && isQuoteChar(line, i) && !lineCommented && !blockCommented) {
|
||||||
|
// Start a quote block
|
||||||
|
quoteStart = i;
|
||||||
|
if (current.length() == 0) {
|
||||||
|
quotedWord = true;
|
||||||
|
if (context == ParseContext.SPLIT_LINE) {
|
||||||
|
current.append(line.charAt(i));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current.append(line.charAt(i));
|
||||||
|
}
|
||||||
|
} else if (quoteStart >= 0 && line.charAt(quoteStart) == line.charAt(i) && !isEscaped(line, i)) {
|
||||||
|
// End quote block
|
||||||
|
if (!quotedWord || context == ParseContext.SPLIT_LINE) {
|
||||||
|
current.append(line.charAt(i));
|
||||||
|
} else if (rawWordCursor >= 0 && rawWordLength < 0) {
|
||||||
|
rawWordLength = i - rawWordStart + 1;
|
||||||
|
}
|
||||||
|
quoteStart = -1;
|
||||||
|
quotedWord = false;
|
||||||
|
} else if (quoteStart < 0 && isDelimiter(line, i)) {
|
||||||
|
if (lineCommented) {
|
||||||
|
if (isCommentDelim(line, i, System.lineSeparator())) {
|
||||||
|
lineCommented = false;
|
||||||
|
}
|
||||||
|
} else if (blockCommented) {
|
||||||
|
if (isCommentDelim(line, i, blockCommentEnd)) {
|
||||||
|
blockCommented = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Delimiter
|
||||||
|
rawWordLength = handleDelimiterAndGetRawWordLength(
|
||||||
|
current, words, rawWordStart, rawWordCursor, rawWordLength, i);
|
||||||
|
rawWordStart = i + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (quoteStart < 0 && !blockCommented && (lineCommented || isLineCommentStarted(line, i))) {
|
||||||
|
lineCommented = true;
|
||||||
|
} else if (quoteStart < 0
|
||||||
|
&& !lineCommented
|
||||||
|
&& (blockCommented || isCommentDelim(line, i, blockCommentStart))) {
|
||||||
|
if (blockCommented) {
|
||||||
|
if (blockCommentEnd != null && isCommentDelim(line, i, blockCommentEnd)) {
|
||||||
|
blockCommented = false;
|
||||||
|
i += blockCommentEnd.length() - 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
blockCommented = true;
|
||||||
|
rawWordLength = handleDelimiterAndGetRawWordLength(
|
||||||
|
current, words, rawWordStart, rawWordCursor, rawWordLength, i);
|
||||||
|
i += blockCommentStart == null ? 0 : blockCommentStart.length() - 1;
|
||||||
|
rawWordStart = i + 1;
|
||||||
|
}
|
||||||
|
} else if (quoteStart < 0 && !lineCommented && isCommentDelim(line, i, blockCommentEnd)) {
|
||||||
|
current.append(line.charAt(i));
|
||||||
|
blockCommentInRightOrder = false;
|
||||||
|
} else if (!isEscapeChar(line, i)) {
|
||||||
|
current.append(line.charAt(i));
|
||||||
|
if (quoteStart < 0) {
|
||||||
|
bracketChecker.check(line, i);
|
||||||
|
}
|
||||||
|
} else if (context == ParseContext.SPLIT_LINE) {
|
||||||
|
current.append(line.charAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.length() > 0 || cursor == line.length()) {
|
||||||
|
words.add(current.toString());
|
||||||
|
if (rawWordCursor >= 0 && rawWordLength < 0) {
|
||||||
|
rawWordLength = line.length() - rawWordStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor == line.length()) {
|
||||||
|
wordIndex = words.size() - 1;
|
||||||
|
wordCursor = words.get(words.size() - 1).length();
|
||||||
|
rawWordCursor = cursor - rawWordStart;
|
||||||
|
rawWordLength = rawWordCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context != ParseContext.COMPLETE && context != ParseContext.SPLIT_LINE) {
|
||||||
|
if (eofOnEscapedNewLine && isEscapeChar(line, line.length() - 1)) {
|
||||||
|
throw new EOFError(-1, -1, "Escaped new line", "newline");
|
||||||
|
}
|
||||||
|
if (eofOnUnclosedQuote && quoteStart >= 0) {
|
||||||
|
throw new EOFError(
|
||||||
|
-1, -1, "Missing closing quote", line.charAt(quoteStart) == '\'' ? "quote" : "dquote");
|
||||||
|
}
|
||||||
|
if (blockCommented) {
|
||||||
|
throw new EOFError(-1, -1, "Missing closing block comment delimiter", "add: " + blockCommentEnd);
|
||||||
|
}
|
||||||
|
if (!blockCommentInRightOrder) {
|
||||||
|
throw new EOFError(-1, -1, "Missing opening block comment delimiter", "missing: " + blockCommentStart);
|
||||||
|
}
|
||||||
|
if (bracketChecker.isClosingBracketMissing() || bracketChecker.isOpeningBracketMissing()) {
|
||||||
|
String message = null;
|
||||||
|
String missing = null;
|
||||||
|
if (bracketChecker.isClosingBracketMissing()) {
|
||||||
|
message = "Missing closing brackets";
|
||||||
|
missing = "add: " + bracketChecker.getMissingClosingBrackets();
|
||||||
|
} else {
|
||||||
|
message = "Missing opening bracket";
|
||||||
|
missing = "missing: " + bracketChecker.getMissingOpeningBracket();
|
||||||
|
}
|
||||||
|
throw new EOFError(
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
message,
|
||||||
|
missing,
|
||||||
|
bracketChecker.getOpenBrackets(),
|
||||||
|
bracketChecker.getNextClosingBracket());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String openingQuote = quotedWord ? line.substring(quoteStart, quoteStart + 1) : null;
|
||||||
|
return new ArgumentList(line, words, wordIndex, wordCursor, cursor, openingQuote, rawWordCursor, rawWordLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the specified character is a whitespace parameter. Check to ensure that the character is not
|
||||||
|
* escaped by any of {@link #getQuoteChars}, and is not escaped by any of the {@link #getEscapeChars}, and
|
||||||
|
* returns true from {@link #isDelimiterChar}.
|
||||||
|
*
|
||||||
|
* @param buffer The complete command buffer
|
||||||
|
* @param pos The index of the character in the buffer
|
||||||
|
* @return True if the character should be a delimiter
|
||||||
|
*/
|
||||||
|
public boolean isDelimiter(final CharSequence buffer, final int pos) {
|
||||||
|
return !isQuoted(buffer, pos) && !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int handleDelimiterAndGetRawWordLength(
|
||||||
|
StringBuilder current,
|
||||||
|
List<String> words,
|
||||||
|
int rawWordStart,
|
||||||
|
int rawWordCursor,
|
||||||
|
int rawWordLength,
|
||||||
|
int pos) {
|
||||||
|
if (current.length() > 0) {
|
||||||
|
words.add(current.toString());
|
||||||
|
current.setLength(0); // reset the arg
|
||||||
|
if (rawWordCursor >= 0 && rawWordLength < 0) {
|
||||||
|
return pos - rawWordStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rawWordLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isQuoted(final CharSequence buffer, final int pos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isQuoteChar(final CharSequence buffer, final int pos) {
|
||||||
|
if (pos < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (quoteChars != null) {
|
||||||
|
for (char e : quoteChars) {
|
||||||
|
if (e == buffer.charAt(pos)) {
|
||||||
|
return !isEscaped(buffer, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCommentDelim(final CharSequence buffer, final int pos, final String pattern) {
|
||||||
|
if (pos < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern != null) {
|
||||||
|
final int length = pattern.length();
|
||||||
|
if (length <= buffer.length() - pos) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (pattern.charAt(i) != buffer.charAt(pos + i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLineCommentStarted(final CharSequence buffer, final int pos) {
|
||||||
|
if (lineCommentDelims != null) {
|
||||||
|
for (String comment : lineCommentDelims) {
|
||||||
|
if (isCommentDelim(buffer, pos, comment)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEscapeChar(char ch) {
|
||||||
|
if (escapeChars != null) {
|
||||||
|
for (char e : escapeChars) {
|
||||||
|
if (e == ch) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this character is a valid escape char (i.e. one that has not been escaped)
|
||||||
|
*
|
||||||
|
* @param buffer the buffer to check in
|
||||||
|
* @param pos the position of the character to check
|
||||||
|
* @return true if the character at the specified position in the given buffer is an escape
|
||||||
|
* character and the character immediately preceding it is not an escape character.
|
||||||
|
*/
|
||||||
|
public boolean isEscapeChar(final CharSequence buffer, final int pos) {
|
||||||
|
if (pos < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char ch = buffer.charAt(pos);
|
||||||
|
return isEscapeChar(ch) && !isEscaped(buffer, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a character is escaped (i.e. if the previous character is an escape)
|
||||||
|
*
|
||||||
|
* @param buffer the buffer to check in
|
||||||
|
* @param pos the position of the character to check
|
||||||
|
* @return true if the character at the specified position in the given buffer is an escape
|
||||||
|
* character and the character immediately preceding it is an escape character.
|
||||||
|
*/
|
||||||
|
public boolean isEscaped(final CharSequence buffer, final int pos) {
|
||||||
|
if (pos <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isEscapeChar(buffer, pos - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the character at the specified position if a delimiter. This method will only be called if
|
||||||
|
* the character is not enclosed in any of the {@link #getQuoteChars}, and is not escaped by any of the
|
||||||
|
* {@link #getEscapeChars}. To perform escaping manually, override {@link #isDelimiter} instead.
|
||||||
|
*
|
||||||
|
* @param buffer the buffer to check in
|
||||||
|
* @param pos the position of the character to check
|
||||||
|
* @return true if the character at the specified position in the given buffer is a delimiter.
|
||||||
|
*/
|
||||||
|
public boolean isDelimiterChar(CharSequence buffer, int pos) {
|
||||||
|
return Character.isWhitespace(buffer.charAt(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRawEscapeChar(char key) {
|
||||||
|
if (escapeChars != null) {
|
||||||
|
for (char e : escapeChars) {
|
||||||
|
if (e == key) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRawQuoteChar(char key) {
|
||||||
|
if (quoteChars != null) {
|
||||||
|
for (char e : quoteChars) {
|
||||||
|
if (e == key) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Bracket {
|
||||||
|
ROUND, // ()
|
||||||
|
CURLY, // {}
|
||||||
|
SQUARE, // []
|
||||||
|
ANGLE // <>
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BlockCommentDelims {
|
||||||
|
private final String start;
|
||||||
|
private final String end;
|
||||||
|
|
||||||
|
public BlockCommentDelims(String start, String end) {
|
||||||
|
if (start == null || end == null || start.isEmpty() || end.isEmpty() || start.equals(end)) {
|
||||||
|
throw new IllegalArgumentException("Bad block comment delimiter!");
|
||||||
|
}
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BracketChecker {
|
||||||
|
private int missingOpeningBracket = -1;
|
||||||
|
private final List<Integer> nested = new ArrayList<>();
|
||||||
|
private int openBrackets = 0;
|
||||||
|
private final int cursor;
|
||||||
|
private String nextClosingBracket;
|
||||||
|
|
||||||
|
public BracketChecker(int cursor) {
|
||||||
|
this.cursor = cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void check(final CharSequence buffer, final int pos) {
|
||||||
|
if (openingBrackets == null || pos < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int bid = bracketId(openingBrackets, buffer, pos);
|
||||||
|
if (bid >= 0) {
|
||||||
|
nested.add(bid);
|
||||||
|
} else {
|
||||||
|
bid = bracketId(closingBrackets, buffer, pos);
|
||||||
|
if (bid >= 0) {
|
||||||
|
if (!nested.isEmpty() && bid == nested.get(nested.size() - 1)) {
|
||||||
|
nested.remove(nested.size() - 1);
|
||||||
|
} else {
|
||||||
|
missingOpeningBracket = bid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cursor > pos) {
|
||||||
|
openBrackets = nested.size();
|
||||||
|
if (nested.size() > 0) {
|
||||||
|
nextClosingBracket = String.valueOf(closingBrackets[nested.get(nested.size() - 1)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOpeningBracketMissing() {
|
||||||
|
return missingOpeningBracket != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMissingOpeningBracket() {
|
||||||
|
if (!isOpeningBracketMissing()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Character.toString(openingBrackets[missingOpeningBracket]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosingBracketMissing() {
|
||||||
|
return !nested.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMissingClosingBrackets() {
|
||||||
|
if (!isClosingBracketMissing()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StringBuilder out = new StringBuilder();
|
||||||
|
for (int i = nested.size() - 1; i > -1; i--) {
|
||||||
|
out.append(closingBrackets[nested.get(i)]);
|
||||||
|
}
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOpenBrackets() {
|
||||||
|
return openBrackets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNextClosingBracket() {
|
||||||
|
return nested.size() == 2 ? nextClosingBracket : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int bracketId(final char[] brackets, final CharSequence buffer, final int pos) {
|
||||||
|
for (int i = 0; i < brackets.length; i++) {
|
||||||
|
if (buffer.charAt(pos) == brackets[i]) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a delimited buffer.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||||
|
*/
|
||||||
|
public class ArgumentList implements ParsedLine, CompletingParsedLine {
|
||||||
|
private final String line;
|
||||||
|
|
||||||
|
private final List<String> words;
|
||||||
|
|
||||||
|
private final int wordIndex;
|
||||||
|
|
||||||
|
private final int wordCursor;
|
||||||
|
|
||||||
|
private final int cursor;
|
||||||
|
|
||||||
|
private final String openingQuote;
|
||||||
|
|
||||||
|
private final int rawWordCursor;
|
||||||
|
|
||||||
|
private final int rawWordLength;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public ArgumentList(
|
||||||
|
final String line,
|
||||||
|
final List<String> words,
|
||||||
|
final int wordIndex,
|
||||||
|
final int wordCursor,
|
||||||
|
final int cursor) {
|
||||||
|
this(
|
||||||
|
line,
|
||||||
|
words,
|
||||||
|
wordIndex,
|
||||||
|
wordCursor,
|
||||||
|
cursor,
|
||||||
|
null,
|
||||||
|
wordCursor,
|
||||||
|
words.get(wordIndex).length());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param line the command line being edited
|
||||||
|
* @param words the list of words
|
||||||
|
* @param wordIndex the index of the current word in the list of words
|
||||||
|
* @param wordCursor the cursor position within the current word
|
||||||
|
* @param cursor the cursor position within the line
|
||||||
|
* @param openingQuote the opening quote (usually '\"' or '\'') or null
|
||||||
|
* @param rawWordCursor the cursor position inside the raw word (i.e. including quotes and escape characters)
|
||||||
|
* @param rawWordLength the raw word length, including quotes and escape characters
|
||||||
|
*/
|
||||||
|
public ArgumentList(
|
||||||
|
final String line,
|
||||||
|
final List<String> words,
|
||||||
|
final int wordIndex,
|
||||||
|
final int wordCursor,
|
||||||
|
final int cursor,
|
||||||
|
final String openingQuote,
|
||||||
|
final int rawWordCursor,
|
||||||
|
final int rawWordLength) {
|
||||||
|
this.line = line;
|
||||||
|
this.words = Collections.unmodifiableList(Objects.requireNonNull(words));
|
||||||
|
this.wordIndex = wordIndex;
|
||||||
|
this.wordCursor = wordCursor;
|
||||||
|
this.cursor = cursor;
|
||||||
|
this.openingQuote = openingQuote;
|
||||||
|
this.rawWordCursor = rawWordCursor;
|
||||||
|
this.rawWordLength = rawWordLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int wordIndex() {
|
||||||
|
return this.wordIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String word() {
|
||||||
|
// TODO: word() should always be contained in words()
|
||||||
|
if ((wordIndex < 0) || (wordIndex >= words.size())) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return words.get(wordIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int wordCursor() {
|
||||||
|
return this.wordCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> words() {
|
||||||
|
return this.words;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int cursor() {
|
||||||
|
return this.cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String line() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence escape(CharSequence candidate, boolean complete) {
|
||||||
|
StringBuilder sb = new StringBuilder(candidate);
|
||||||
|
Predicate<Integer> needToBeEscaped;
|
||||||
|
String quote = openingQuote;
|
||||||
|
boolean middleQuotes = false;
|
||||||
|
if (openingQuote == null) {
|
||||||
|
for (int i = 0; i < sb.length(); i++) {
|
||||||
|
if (isQuoteChar(sb, i)) {
|
||||||
|
middleQuotes = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (escapeChars != null) {
|
||||||
|
if (escapeChars.length > 0) {
|
||||||
|
// Completion is protected by an opening quote:
|
||||||
|
// Delimiters (spaces) don't need to be escaped, nor do other quotes, but everything else does.
|
||||||
|
// Also, close the quote at the end
|
||||||
|
if (openingQuote != null) {
|
||||||
|
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i))
|
||||||
|
|| String.valueOf(sb.charAt(i)).equals(openingQuote);
|
||||||
|
}
|
||||||
|
// Completion is protected by middle quotes:
|
||||||
|
// Delimiters (spaces) don't need to be escaped, nor do quotes, but everything else does.
|
||||||
|
else if (middleQuotes) {
|
||||||
|
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i));
|
||||||
|
}
|
||||||
|
// No quote protection, need to escape everything: delimiter chars (spaces), quote chars
|
||||||
|
// and escapes themselves
|
||||||
|
else {
|
||||||
|
needToBeEscaped = i ->
|
||||||
|
isDelimiterChar(sb, i) || isRawEscapeChar(sb.charAt(i)) || isRawQuoteChar(sb.charAt(i));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < sb.length(); i++) {
|
||||||
|
if (needToBeEscaped.test(i)) {
|
||||||
|
sb.insert(i++, escapeChars[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (openingQuote == null && !middleQuotes) {
|
||||||
|
for (int i = 0; i < sb.length(); i++) {
|
||||||
|
if (isDelimiterChar(sb, i)) {
|
||||||
|
quote = "'";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (quote != null) {
|
||||||
|
sb.insert(0, quote);
|
||||||
|
if (complete) {
|
||||||
|
sb.append(quote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int rawWordCursor() {
|
||||||
|
return rawWordCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int rawWordLength() {
|
||||||
|
return rawWordLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
393
net-cli/src/main/java/org/jline/reader/impl/InputRC.java
Normal file
393
net-cli/src/main/java/org/jline/reader/impl/InputRC.java
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2023, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.Macro;
|
||||||
|
import org.jline.reader.Reference;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
|
||||||
|
public final class InputRC {
|
||||||
|
|
||||||
|
private final LineReader reader;
|
||||||
|
|
||||||
|
private InputRC(LineReader reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void configure(LineReader reader, URL url) throws IOException {
|
||||||
|
try (InputStream is = url.openStream()) {
|
||||||
|
configure(reader, is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void configure(LineReader reader, InputStream is) throws IOException {
|
||||||
|
try (InputStreamReader r = new InputStreamReader(is)) {
|
||||||
|
configure(reader, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void configure(LineReader reader, Reader r) throws IOException {
|
||||||
|
BufferedReader br;
|
||||||
|
if (r instanceof BufferedReader) {
|
||||||
|
br = (BufferedReader) r;
|
||||||
|
} else {
|
||||||
|
br = new BufferedReader(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
Terminal terminal = reader.getTerminal();
|
||||||
|
|
||||||
|
if (Terminal.TYPE_DUMB.equals(terminal.getType()) || Terminal.TYPE_DUMB_COLOR.equals(terminal.getType())) {
|
||||||
|
reader.getVariables().putIfAbsent(LineReader.EDITING_MODE, "dumb");
|
||||||
|
} else {
|
||||||
|
reader.getVariables().putIfAbsent(LineReader.EDITING_MODE, "emacs");
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.setKeyMap(LineReader.MAIN);
|
||||||
|
new InputRC(reader).parse(br);
|
||||||
|
if ("vi".equals(reader.getVariable(LineReader.EDITING_MODE))) {
|
||||||
|
reader.getKeyMaps().put(LineReader.MAIN, reader.getKeyMaps().get(LineReader.VIINS));
|
||||||
|
} else if ("emacs".equals(reader.getVariable(LineReader.EDITING_MODE))) {
|
||||||
|
reader.getKeyMaps().put(LineReader.MAIN, reader.getKeyMaps().get(LineReader.EMACS));
|
||||||
|
} else if ("dumb".equals(reader.getVariable(LineReader.EDITING_MODE))) {
|
||||||
|
reader.getKeyMaps().put(LineReader.MAIN, reader.getKeyMaps().get(LineReader.DUMB));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String translateQuoted(String keySeq) {
|
||||||
|
int i;
|
||||||
|
String str = keySeq.substring(1, keySeq.length() - 1);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (i = 0; i < str.length(); i++) {
|
||||||
|
char c = str.charAt(i);
|
||||||
|
if (c == '\\') {
|
||||||
|
boolean ctrl = str.regionMatches(i, "\\C-", 0, 3) || str.regionMatches(i, "\\M-\\C-", 0, 6);
|
||||||
|
boolean meta = str.regionMatches(i, "\\M-", 0, 3) || str.regionMatches(i, "\\C-\\M-", 0, 6);
|
||||||
|
i += (meta ? 3 : 0) + (ctrl ? 3 : 0) + (!meta && !ctrl ? 1 : 0);
|
||||||
|
if (i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = str.charAt(i);
|
||||||
|
if (meta) {
|
||||||
|
sb.append("\u001b");
|
||||||
|
}
|
||||||
|
if (ctrl) {
|
||||||
|
c = c == '?' ? 0x7f : (char) (Character.toUpperCase(c) & 0x1f);
|
||||||
|
}
|
||||||
|
if (!meta && !ctrl) {
|
||||||
|
switch (c) {
|
||||||
|
case 'a':
|
||||||
|
c = 0x07;
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
c = '\b';
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
c = 0x7f;
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
c = 0x1b;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
c = '\f';
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
c = '\n';
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
c = '\r';
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
c = '\t';
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
c = 0x0b;
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
c = '\\';
|
||||||
|
break;
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
c = 0;
|
||||||
|
for (int j = 0; j < 3; j++, i++) {
|
||||||
|
if (i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int k = Character.digit(str.charAt(i), 8);
|
||||||
|
if (k < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = (char) (c * 8 + k);
|
||||||
|
}
|
||||||
|
c &= 0xFF;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
i++;
|
||||||
|
c = 0;
|
||||||
|
for (int j = 0; j < 2; j++, i++) {
|
||||||
|
if (i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int k = Character.digit(str.charAt(i), 16);
|
||||||
|
if (k < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = (char) (c * 16 + k);
|
||||||
|
}
|
||||||
|
c &= 0xFF;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
i++;
|
||||||
|
c = 0;
|
||||||
|
for (int j = 0; j < 4; j++, i++) {
|
||||||
|
if (i >= str.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int k = Character.digit(str.charAt(i), 16);
|
||||||
|
if (k < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c = (char) (c * 16 + k);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(c);
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char getKeyFromName(String name) {
|
||||||
|
if ("DEL".equalsIgnoreCase(name) || "Rubout".equalsIgnoreCase(name)) {
|
||||||
|
return 0x7f;
|
||||||
|
} else if ("ESC".equalsIgnoreCase(name) || "Escape".equalsIgnoreCase(name)) {
|
||||||
|
return '\033';
|
||||||
|
} else if ("LFD".equalsIgnoreCase(name) || "NewLine".equalsIgnoreCase(name)) {
|
||||||
|
return '\n';
|
||||||
|
} else if ("RET".equalsIgnoreCase(name) || "Return".equalsIgnoreCase(name)) {
|
||||||
|
return '\r';
|
||||||
|
} else if ("SPC".equalsIgnoreCase(name) || "Space".equalsIgnoreCase(name)) {
|
||||||
|
return ' ';
|
||||||
|
} else if ("Tab".equalsIgnoreCase(name)) {
|
||||||
|
return '\t';
|
||||||
|
} else {
|
||||||
|
return name.charAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setVar(LineReader reader, String key, String val) {
|
||||||
|
if (LineReader.KEYMAP.equalsIgnoreCase(key)) {
|
||||||
|
reader.setKeyMap(val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (LineReader.Option option : LineReader.Option.values()) {
|
||||||
|
if (option.name().toLowerCase(Locale.ENGLISH).replace('_', '-').equals(val)) {
|
||||||
|
if ("on".equalsIgnoreCase(val)) {
|
||||||
|
reader.setOpt(option);
|
||||||
|
} else if ("off".equalsIgnoreCase(val)) {
|
||||||
|
reader.unsetOpt(option);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.setVariable(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parse(BufferedReader br) throws IOException, IllegalArgumentException {
|
||||||
|
String line;
|
||||||
|
boolean parsing = true;
|
||||||
|
List<Boolean> ifsStack = new ArrayList<>();
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
try {
|
||||||
|
line = line.trim();
|
||||||
|
if (line.length() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.charAt(0) == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
if (line.charAt(i) == '$') {
|
||||||
|
String cmd;
|
||||||
|
String args;
|
||||||
|
++i;
|
||||||
|
while (i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
int s = i;
|
||||||
|
while (i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
cmd = line.substring(s, i);
|
||||||
|
while (i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
s = i;
|
||||||
|
while (i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
args = line.substring(s, i);
|
||||||
|
if ("if".equalsIgnoreCase(cmd)) {
|
||||||
|
ifsStack.add(parsing);
|
||||||
|
if (!parsing) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (args.startsWith("term=")) {
|
||||||
|
// TODO
|
||||||
|
} else if (args.startsWith("mode=")) {
|
||||||
|
String mode = (String) reader.getVariable(LineReader.EDITING_MODE);
|
||||||
|
parsing = args.substring("mode=".length()).equalsIgnoreCase(mode);
|
||||||
|
} else {
|
||||||
|
parsing = args.equalsIgnoreCase(reader.getAppName());
|
||||||
|
}
|
||||||
|
} else if ("else".equalsIgnoreCase(cmd)) {
|
||||||
|
if (ifsStack.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("$else found without matching $if");
|
||||||
|
}
|
||||||
|
boolean invert = true;
|
||||||
|
for (boolean b : ifsStack) {
|
||||||
|
if (!b) {
|
||||||
|
invert = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (invert) {
|
||||||
|
parsing = !parsing;
|
||||||
|
}
|
||||||
|
} else if ("endif".equalsIgnoreCase(cmd)) {
|
||||||
|
if (ifsStack.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("endif found without matching $if");
|
||||||
|
}
|
||||||
|
parsing = ifsStack.remove(ifsStack.size() - 1);
|
||||||
|
} else if ("include".equalsIgnoreCase(cmd)) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!parsing) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.charAt(i++) == '"') {
|
||||||
|
boolean esc = false;
|
||||||
|
for (; ; i++) {
|
||||||
|
if (i >= line.length()) {
|
||||||
|
throw new IllegalArgumentException("Missing closing quote on line '" + line + "'");
|
||||||
|
}
|
||||||
|
if (esc) {
|
||||||
|
esc = false;
|
||||||
|
} else if (line.charAt(i) == '\\') {
|
||||||
|
esc = true;
|
||||||
|
} else if (line.charAt(i) == '"') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (i < line.length() && line.charAt(i) != ':' && line.charAt(i) != ' ' && line.charAt(i) != '\t') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
String keySeq = line.substring(0, i);
|
||||||
|
boolean equivalency = i + 1 < line.length() && line.charAt(i) == ':' && line.charAt(i + 1) == '=';
|
||||||
|
i++;
|
||||||
|
if (equivalency) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (keySeq.equalsIgnoreCase("set")) {
|
||||||
|
String key;
|
||||||
|
String val;
|
||||||
|
while (i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
int s = i;
|
||||||
|
while (i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
key = line.substring(s, i);
|
||||||
|
while (i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
s = i;
|
||||||
|
while (i < line.length() && (line.charAt(i) != ' ' && line.charAt(i) != '\t')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
val = line.substring(s, i);
|
||||||
|
setVar(reader, key, val);
|
||||||
|
} else {
|
||||||
|
while (i < line.length() && (line.charAt(i) == ' ' || line.charAt(i) == '\t')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
int start = i;
|
||||||
|
if (i < line.length() && (line.charAt(i) == '\'' || line.charAt(i) == '\"')) {
|
||||||
|
char delim = line.charAt(i++);
|
||||||
|
boolean esc = false;
|
||||||
|
for (; ; i++) {
|
||||||
|
if (i >= line.length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (esc) {
|
||||||
|
esc = false;
|
||||||
|
} else if (line.charAt(i) == '\\') {
|
||||||
|
esc = true;
|
||||||
|
} else if (line.charAt(i) == delim) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < line.length() && line.charAt(i) != ' ' && line.charAt(i) != '\t'; i++)
|
||||||
|
;
|
||||||
|
String val = line.substring(Math.min(start, line.length()), Math.min(i, line.length()));
|
||||||
|
if (keySeq.charAt(0) == '"') {
|
||||||
|
keySeq = translateQuoted(keySeq);
|
||||||
|
} else {
|
||||||
|
// Bind key name
|
||||||
|
String keyName =
|
||||||
|
keySeq.lastIndexOf('-') > 0 ? keySeq.substring(keySeq.lastIndexOf('-') + 1) : keySeq;
|
||||||
|
char key = getKeyFromName(keyName);
|
||||||
|
keyName = keySeq.toLowerCase();
|
||||||
|
keySeq = "";
|
||||||
|
if (keyName.contains("meta-") || keyName.contains("m-")) {
|
||||||
|
keySeq += "\u001b";
|
||||||
|
}
|
||||||
|
if (keyName.contains("control-") || keyName.contains("c-") || keyName.contains("ctrl-")) {
|
||||||
|
key = (char) (Character.toUpperCase(key) & 0x1f);
|
||||||
|
}
|
||||||
|
keySeq += key;
|
||||||
|
}
|
||||||
|
if (val.length() > 0 && (val.charAt(0) == '\'' || val.charAt(0) == '\"')) {
|
||||||
|
reader.getKeys().bind(new Macro(translateQuoted(val)), keySeq);
|
||||||
|
} else {
|
||||||
|
reader.getKeys().bind(new Reference(val), keySeq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.warn("Unable to parse user configuration: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
176
net-cli/src/main/java/org/jline/reader/impl/KillRing.java
Normal file
176
net-cli/src/main/java/org/jline/reader/impl/KillRing.java
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The kill ring class keeps killed text in a fixed size ring. In this
|
||||||
|
* class we also keep record of whether or not the last command was a
|
||||||
|
* kill or a yank. Depending on this, the class may behave
|
||||||
|
* different. For instance, two consecutive kill-word commands fill
|
||||||
|
* the same slot such that the next yank will return the two
|
||||||
|
* previously killed words instead that only the last one. Likewise
|
||||||
|
* yank pop requires that the previous command was either a yank or a
|
||||||
|
* yank-pop.
|
||||||
|
*/
|
||||||
|
public final class KillRing {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default size is 60, like in emacs.
|
||||||
|
*/
|
||||||
|
private static final int DEFAULT_SIZE = 60;
|
||||||
|
|
||||||
|
private final String[] slots;
|
||||||
|
private int head = 0;
|
||||||
|
private boolean lastKill = false;
|
||||||
|
private boolean lastYank = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new kill ring of the given size.
|
||||||
|
*
|
||||||
|
* @param size the size of the ring
|
||||||
|
*/
|
||||||
|
public KillRing(int size) {
|
||||||
|
slots = new String[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new kill ring of the default size. See {@link #DEFAULT_SIZE}.
|
||||||
|
*/
|
||||||
|
public KillRing() {
|
||||||
|
this(DEFAULT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the last-yank state.
|
||||||
|
*/
|
||||||
|
public void resetLastYank() {
|
||||||
|
lastYank = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the last-kill state.
|
||||||
|
*/
|
||||||
|
public void resetLastKill() {
|
||||||
|
lastKill = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the last command was a yank.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the last command was a yank
|
||||||
|
*/
|
||||||
|
public boolean lastYank() {
|
||||||
|
return lastYank;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the string to the kill-ring. Also sets lastYank to false
|
||||||
|
* and lastKill to true.
|
||||||
|
*
|
||||||
|
* @param str the string to add
|
||||||
|
*/
|
||||||
|
public void add(String str) {
|
||||||
|
lastYank = false;
|
||||||
|
|
||||||
|
if (lastKill) {
|
||||||
|
if (slots[head] != null) {
|
||||||
|
slots[head] += str;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastKill = true;
|
||||||
|
next();
|
||||||
|
slots[head] = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the string to the kill-ring product of killing
|
||||||
|
* backwards. If the previous command was a kill text one then
|
||||||
|
* adds the text at the beginning of the previous kill to avoid
|
||||||
|
* that two consecutive backwards kills followed by a yank leaves
|
||||||
|
* things reversed.
|
||||||
|
*
|
||||||
|
* @param str the string to add
|
||||||
|
*/
|
||||||
|
public void addBackwards(String str) {
|
||||||
|
lastYank = false;
|
||||||
|
|
||||||
|
if (lastKill) {
|
||||||
|
if (slots[head] != null) {
|
||||||
|
slots[head] = str + slots[head];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastKill = true;
|
||||||
|
next();
|
||||||
|
slots[head] = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yanks a previously killed text. Returns {@code null} if the
|
||||||
|
* ring is empty.
|
||||||
|
*
|
||||||
|
* @return the text in the current position
|
||||||
|
*/
|
||||||
|
public String yank() {
|
||||||
|
lastKill = false;
|
||||||
|
lastYank = true;
|
||||||
|
return slots[head];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the pointer to the current slot back and returns the text
|
||||||
|
* in that position. If the previous command was not yank returns
|
||||||
|
* null.
|
||||||
|
*
|
||||||
|
* @return the text in the previous position
|
||||||
|
*/
|
||||||
|
public String yankPop() {
|
||||||
|
lastKill = false;
|
||||||
|
if (lastYank) {
|
||||||
|
prev();
|
||||||
|
return slots[head];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the pointer to the current slot forward. If the end of
|
||||||
|
* the slots is reached then points back to the beginning.
|
||||||
|
*/
|
||||||
|
private void next() {
|
||||||
|
if (head == 0 && slots[0] == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
head++;
|
||||||
|
if (head == slots.length) {
|
||||||
|
head = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the pointer to the current slot backwards. If the
|
||||||
|
* beginning of the slots is reached then traverses the slot
|
||||||
|
* backwards until one with not null content is found.
|
||||||
|
*/
|
||||||
|
private void prev() {
|
||||||
|
head--;
|
||||||
|
if (head == -1) {
|
||||||
|
int x = (slots.length - 1);
|
||||||
|
for (; x >= 0; x--) {
|
||||||
|
if (slots[x] != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
head = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6429
net-cli/src/main/java/org/jline/reader/impl/LineReaderImpl.java
Normal file
6429
net-cli/src/main/java/org/jline/reader/impl/LineReaderImpl.java
Normal file
File diff suppressed because it is too large
Load diff
80
net-cli/src/main/java/org/jline/reader/impl/ReaderUtils.java
Normal file
80
net-cli/src/main/java/org/jline/reader/impl/ReaderUtils.java
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.utils.Levenshtein;
|
||||||
|
|
||||||
|
public class ReaderUtils {
|
||||||
|
|
||||||
|
private ReaderUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSet(LineReader reader, LineReader.Option option) {
|
||||||
|
return reader != null && reader.isSet(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getString(LineReader reader, String name, String def) {
|
||||||
|
Object v = reader != null ? reader.getVariable(name) : null;
|
||||||
|
return v != null ? v.toString() : def;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getBoolean(LineReader reader, String name, boolean def) {
|
||||||
|
Object v = reader != null ? reader.getVariable(name) : null;
|
||||||
|
if (v instanceof Boolean) {
|
||||||
|
return (Boolean) v;
|
||||||
|
} else if (v != null) {
|
||||||
|
String s = v.toString();
|
||||||
|
return s.isEmpty() || s.equalsIgnoreCase("on") || s.equalsIgnoreCase("1") || s.equalsIgnoreCase("true");
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getInt(LineReader reader, String name, int def) {
|
||||||
|
int nb = def;
|
||||||
|
Object v = reader != null ? reader.getVariable(name) : null;
|
||||||
|
if (v instanceof Number) {
|
||||||
|
return ((Number) v).intValue();
|
||||||
|
} else if (v != null) {
|
||||||
|
nb = 0;
|
||||||
|
try {
|
||||||
|
nb = Integer.parseInt(v.toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getLong(LineReader reader, String name, long def) {
|
||||||
|
long nb = def;
|
||||||
|
Object v = reader != null ? reader.getVariable(name) : null;
|
||||||
|
if (v instanceof Number) {
|
||||||
|
return ((Number) v).longValue();
|
||||||
|
} else if (v != null) {
|
||||||
|
nb = 0;
|
||||||
|
try {
|
||||||
|
nb = Long.parseLong(v.toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int distance(String word, String cand) {
|
||||||
|
if (word.length() < cand.length()) {
|
||||||
|
int d1 = Levenshtein.distance(word, cand.substring(0, Math.min(cand.length(), word.length())));
|
||||||
|
int d2 = Levenshtein.distance(word, cand);
|
||||||
|
return Math.min(d1, d2);
|
||||||
|
} else {
|
||||||
|
return Levenshtein.distance(word, cand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2017, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.jline.reader.MaskingCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple {@link MaskingCallback} that will replace all the characters in the line with the given mask.
|
||||||
|
* If the given mask is equal to {@link LineReaderImpl#NULL_MASK} then the line will be replaced with an empty String.
|
||||||
|
*/
|
||||||
|
public final class SimpleMaskingCallback implements MaskingCallback {
|
||||||
|
private final Character mask;
|
||||||
|
|
||||||
|
public SimpleMaskingCallback(Character mask) {
|
||||||
|
this.mask = Objects.requireNonNull(mask, "mask must be a non null character");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String display(String line) {
|
||||||
|
if (mask.equals(LineReaderImpl.NULL_MASK)) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
StringBuilder sb = new StringBuilder(line.length());
|
||||||
|
for (int i = line.length(); i-- > 0; ) {
|
||||||
|
sb.append((char) mask);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String history(String line) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
75
net-cli/src/main/java/org/jline/reader/impl/UndoTree.java
Normal file
75
net-cli/src/main/java/org/jline/reader/impl/UndoTree.java
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple undo tree.
|
||||||
|
* Note that the first added state can't be undone
|
||||||
|
*/
|
||||||
|
public class UndoTree<T> {
|
||||||
|
|
||||||
|
private final Consumer<T> state;
|
||||||
|
private final Node parent;
|
||||||
|
private Node current;
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public UndoTree(Consumer<T> s) {
|
||||||
|
state = s;
|
||||||
|
parent = new Node(null);
|
||||||
|
parent.left = parent;
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void newState(T state) {
|
||||||
|
Node node = new Node(state);
|
||||||
|
current.right = node;
|
||||||
|
node.left = current;
|
||||||
|
current = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canUndo() {
|
||||||
|
return current.left != parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canRedo() {
|
||||||
|
return current.right != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void undo() {
|
||||||
|
if (!canUndo()) {
|
||||||
|
throw new IllegalStateException("Cannot undo.");
|
||||||
|
}
|
||||||
|
current = current.left;
|
||||||
|
state.accept(current.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void redo() {
|
||||||
|
if (!canRedo()) {
|
||||||
|
throw new IllegalStateException("Cannot redo.");
|
||||||
|
}
|
||||||
|
current = current.right;
|
||||||
|
state.accept(current.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Node {
|
||||||
|
private final T state;
|
||||||
|
private Node left = null;
|
||||||
|
private Node right = null;
|
||||||
|
|
||||||
|
public Node(T s) {
|
||||||
|
state = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.completer;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completer which contains multiple completers and aggregates them together.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||||
|
* @since 2.3
|
||||||
|
*/
|
||||||
|
public class AggregateCompleter implements Completer {
|
||||||
|
private final Collection<Completer> completers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an AggregateCompleter with the given completers.
|
||||||
|
* The completers will be used in the order given.
|
||||||
|
*
|
||||||
|
* @param completers the completers
|
||||||
|
*/
|
||||||
|
public AggregateCompleter(final Completer... completers) {
|
||||||
|
this(Arrays.asList(completers));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an AggregateCompleter with the given completers.
|
||||||
|
* The completers will be used in the order given.
|
||||||
|
*
|
||||||
|
* @param completers the completers
|
||||||
|
*/
|
||||||
|
public AggregateCompleter(Collection<Completer> completers) {
|
||||||
|
assert completers != null;
|
||||||
|
this.completers = completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the collection of completers currently being aggregated.
|
||||||
|
*
|
||||||
|
* @return the aggregated completers
|
||||||
|
*/
|
||||||
|
public Collection<Completer> getCompleters() {
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a completion operation across all aggregated completers.
|
||||||
|
* <p>
|
||||||
|
* The effect is similar to the following code:
|
||||||
|
* <blockquote><pre>{@code completers.forEach(c -> c.complete(reader, line, candidates));}</pre></blockquote>
|
||||||
|
*
|
||||||
|
* @see Completer#complete(LineReader, ParsedLine, List)
|
||||||
|
*/
|
||||||
|
public void complete(LineReader reader, final ParsedLine line, final List<Candidate> candidates) {
|
||||||
|
Objects.requireNonNull(line);
|
||||||
|
Objects.requireNonNull(candidates);
|
||||||
|
completers.forEach(c -> c.complete(reader, line, candidates));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a string representing the aggregated completers
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "{" + "completers=" + completers + '}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2019, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.completer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Completer} implementation that invokes a child completer using the appropriate <i>separator</i> argument.
|
||||||
|
* This can be used instead of the individual completers having to know about argument parsing semantics.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||||
|
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||||
|
* @since 2.3
|
||||||
|
*/
|
||||||
|
public class ArgumentCompleter implements Completer {
|
||||||
|
private final List<Completer> completers = new ArrayList<>();
|
||||||
|
|
||||||
|
private boolean strict = true;
|
||||||
|
private boolean strictCommand = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new completer.
|
||||||
|
*
|
||||||
|
* @param completers The embedded completers
|
||||||
|
*/
|
||||||
|
public ArgumentCompleter(final Collection<Completer> completers) {
|
||||||
|
Objects.requireNonNull(completers);
|
||||||
|
this.completers.addAll(completers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new completer.
|
||||||
|
*
|
||||||
|
* @param completers The embedded completers
|
||||||
|
*/
|
||||||
|
public ArgumentCompleter(final Completer... completers) {
|
||||||
|
this(Arrays.asList(completers));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, a completion at argument index N will only succeed
|
||||||
|
* if all the completions from 1-(N-1) also succeed.
|
||||||
|
*
|
||||||
|
* @param strictCommand the strictCommand flag
|
||||||
|
*/
|
||||||
|
public void setStrictCommand(final boolean strictCommand) {
|
||||||
|
this.strictCommand = strictCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a completion at argument index N will success
|
||||||
|
* if all the completions from arguments 0-(N-1) also succeed.
|
||||||
|
*
|
||||||
|
* @return True if strict.
|
||||||
|
* @since 2.3
|
||||||
|
*/
|
||||||
|
public boolean isStrict() {
|
||||||
|
return this.strict;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, a completion at argument index N will only succeed
|
||||||
|
* if all the completions from 0-(N-1) also succeed.
|
||||||
|
*
|
||||||
|
* @param strict the strict flag
|
||||||
|
*/
|
||||||
|
public void setStrict(final boolean strict) {
|
||||||
|
this.strict = strict;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of completers used inside this <code>ArgumentCompleter</code>.
|
||||||
|
*
|
||||||
|
* @return The list of completers.
|
||||||
|
* @since 2.3
|
||||||
|
*/
|
||||||
|
public List<Completer> getCompleters() {
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void complete(LineReader reader, ParsedLine line, final List<Candidate> candidates) {
|
||||||
|
Objects.requireNonNull(line);
|
||||||
|
Objects.requireNonNull(candidates);
|
||||||
|
|
||||||
|
if (line.wordIndex() < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Completer> completers = getCompleters();
|
||||||
|
Completer completer;
|
||||||
|
|
||||||
|
// if we are beyond the end of the completers, just use the last one
|
||||||
|
if (line.wordIndex() >= completers.size()) {
|
||||||
|
completer = completers.get(completers.size() - 1);
|
||||||
|
} else {
|
||||||
|
completer = completers.get(line.wordIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that all the previous completers are successful before allowing this completer to pass (only if
|
||||||
|
// strict).
|
||||||
|
for (int i = strictCommand ? 0 : 1; isStrict() && (i < line.wordIndex()); i++) {
|
||||||
|
int idx = i >= completers.size() ? (completers.size() - 1) : i;
|
||||||
|
if (idx == 0 && !strictCommand) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Completer sub = completers.get(idx);
|
||||||
|
List<? extends CharSequence> args = line.words();
|
||||||
|
String arg = (args == null || i >= args.size()) ? "" : args.get(i).toString();
|
||||||
|
|
||||||
|
List<Candidate> subCandidates = new LinkedList<>();
|
||||||
|
sub.complete(reader, new ArgumentLine(arg, arg.length()), subCandidates);
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
for (Candidate cand : subCandidates) {
|
||||||
|
if (cand.value().equals(arg)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completer.complete(reader, line, candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ArgumentLine implements ParsedLine {
|
||||||
|
private final String word;
|
||||||
|
private final int cursor;
|
||||||
|
|
||||||
|
public ArgumentLine(String word, int cursor) {
|
||||||
|
this.word = word;
|
||||||
|
this.cursor = cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String word() {
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int wordCursor() {
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int wordIndex() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> words() {
|
||||||
|
return Collections.singletonList(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String line() {
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int cursor() {
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.completer;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Completer} for {@link Enum} names.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||||
|
* @since 2.3
|
||||||
|
*/
|
||||||
|
public class EnumCompleter extends StringsCompleter {
|
||||||
|
public EnumCompleter(Class<? extends Enum<?>> source) {
|
||||||
|
Objects.requireNonNull(source);
|
||||||
|
for (Enum<?> n : source.getEnumConstants()) {
|
||||||
|
candidates.add(new Candidate(n.name().toLowerCase()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.completer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.LineReader.Option;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.utils.AttributedStringBuilder;
|
||||||
|
import org.jline.utils.AttributedStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file name completer takes the buffer and issues a list of
|
||||||
|
* potential completions.
|
||||||
|
* <p>
|
||||||
|
* This completer tries to behave as similar as possible to
|
||||||
|
* <i>bash</i>'s file name completion (using GNU readline)
|
||||||
|
* with the following exceptions:
|
||||||
|
* <ul>
|
||||||
|
* <li>Candidates that are directories will end with "/"</li>
|
||||||
|
* <li>Wildcard regular expressions are not evaluated or replaced</li>
|
||||||
|
* <li>The "~" character can be used to represent the user's home,
|
||||||
|
* but it cannot complete to other users' homes, since java does
|
||||||
|
* not provide any way of determining that easily</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||||
|
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||||
|
* @since 2.3
|
||||||
|
* @deprecated use <code>org.jline.builtins.Completers$FileNameCompleter</code> instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public class FileNameCompleter implements Completer {
|
||||||
|
|
||||||
|
public void complete(LineReader reader, ParsedLine commandLine, final List<Candidate> candidates) {
|
||||||
|
assert commandLine != null;
|
||||||
|
assert candidates != null;
|
||||||
|
|
||||||
|
String buffer = commandLine.word().substring(0, commandLine.wordCursor());
|
||||||
|
|
||||||
|
Path current;
|
||||||
|
String curBuf;
|
||||||
|
String sep = getUserDir().getFileSystem().getSeparator();
|
||||||
|
int lastSep = buffer.lastIndexOf(sep);
|
||||||
|
if (lastSep >= 0) {
|
||||||
|
curBuf = buffer.substring(0, lastSep + 1);
|
||||||
|
if (curBuf.startsWith("~")) {
|
||||||
|
if (curBuf.startsWith("~" + sep)) {
|
||||||
|
current = getUserHome().resolve(curBuf.substring(2));
|
||||||
|
} else {
|
||||||
|
current = getUserHome().getParent().resolve(curBuf.substring(1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current = getUserDir().resolve(curBuf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
curBuf = "";
|
||||||
|
current = getUserDir();
|
||||||
|
}
|
||||||
|
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(current, this::accept)) {
|
||||||
|
directoryStream.forEach(p -> {
|
||||||
|
String value = curBuf + p.getFileName().toString();
|
||||||
|
if (Files.isDirectory(p)) {
|
||||||
|
candidates.add(new Candidate(
|
||||||
|
value + (reader.isSet(Option.AUTO_PARAM_SLASH) ? sep : ""),
|
||||||
|
getDisplay(reader.getTerminal(), p),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
reader.isSet(Option.AUTO_REMOVE_SLASH) ? sep : null,
|
||||||
|
null,
|
||||||
|
false));
|
||||||
|
} else {
|
||||||
|
candidates.add(
|
||||||
|
new Candidate(value, getDisplay(reader.getTerminal(), p), null, null, null, null, true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean accept(Path path) {
|
||||||
|
try {
|
||||||
|
return !Files.isHidden(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Path getUserDir() {
|
||||||
|
return Paths.get(System.getProperty("user.dir"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Path getUserHome() {
|
||||||
|
return Paths.get(System.getProperty("user.home"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getDisplay(Terminal terminal, Path p) {
|
||||||
|
// TODO: use $LS_COLORS for output
|
||||||
|
String name = p.getFileName().toString();
|
||||||
|
if (Files.isDirectory(p)) {
|
||||||
|
AttributedStringBuilder sb = new AttributedStringBuilder();
|
||||||
|
sb.styled(AttributedStyle.BOLD.foreground(AttributedStyle.RED), name);
|
||||||
|
sb.append("/");
|
||||||
|
name = sb.toAnsi(terminal);
|
||||||
|
} else if (Files.isSymbolicLink(p)) {
|
||||||
|
AttributedStringBuilder sb = new AttributedStringBuilder();
|
||||||
|
sb.styled(AttributedStyle.BOLD.foreground(AttributedStyle.RED), name);
|
||||||
|
sb.append("@");
|
||||||
|
name = sb.toAnsi(terminal);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.completer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Null completer.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
|
||||||
|
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||||
|
* @since 2.3
|
||||||
|
*/
|
||||||
|
public final class NullCompleter implements Completer {
|
||||||
|
public static final NullCompleter INSTANCE = new NullCompleter();
|
||||||
|
|
||||||
|
public void complete(LineReader reader, final ParsedLine line, final List<Candidate> candidates) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2019, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.completer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completer for a set of strings.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
|
||||||
|
* @since 2.3
|
||||||
|
*/
|
||||||
|
public class StringsCompleter implements Completer {
|
||||||
|
protected Collection<Candidate> candidates;
|
||||||
|
protected Supplier<Collection<String>> stringsSupplier;
|
||||||
|
|
||||||
|
public StringsCompleter() {
|
||||||
|
this(Collections.<Candidate>emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringsCompleter(Supplier<Collection<String>> stringsSupplier) {
|
||||||
|
assert stringsSupplier != null;
|
||||||
|
candidates = null;
|
||||||
|
this.stringsSupplier = stringsSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringsCompleter(String... strings) {
|
||||||
|
this(Arrays.asList(strings));
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringsCompleter(Iterable<String> strings) {
|
||||||
|
assert strings != null;
|
||||||
|
this.candidates = new ArrayList<>();
|
||||||
|
for (String string : strings) {
|
||||||
|
candidates.add(new Candidate(AttributedString.stripAnsi(string), string, null, null, null, null, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringsCompleter(Candidate... candidates) {
|
||||||
|
this(Arrays.asList(candidates));
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringsCompleter(Collection<Candidate> candidates) {
|
||||||
|
assert candidates != null;
|
||||||
|
this.candidates = new ArrayList<>(candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void complete(LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) {
|
||||||
|
assert commandLine != null;
|
||||||
|
assert candidates != null;
|
||||||
|
if (this.candidates != null) {
|
||||||
|
candidates.addAll(this.candidates);
|
||||||
|
} else {
|
||||||
|
for (String string : stringsSupplier.get()) {
|
||||||
|
candidates.add(new Candidate(AttributedString.stripAnsi(string), string, null, null, null, null, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String value = candidates != null ? candidates.toString() : "{" + stringsSupplier.toString() + "}";
|
||||||
|
return "StringsCompleter" + value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2020, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.completer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completer which contains multiple completers and aggregates them together.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
|
||||||
|
*/
|
||||||
|
public class SystemCompleter implements Completer {
|
||||||
|
private Map<String, List<Completer>> completers = new HashMap<>();
|
||||||
|
private final Map<String, String> aliasCommand = new HashMap<>();
|
||||||
|
private StringsCompleter commands;
|
||||||
|
private boolean compiled = false;
|
||||||
|
|
||||||
|
public SystemCompleter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) {
|
||||||
|
if (!compiled) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
assert commandLine != null;
|
||||||
|
assert candidates != null;
|
||||||
|
if (commandLine.words().size() > 0) {
|
||||||
|
if (commandLine.words().size() == 1) {
|
||||||
|
String buffer = commandLine.words().get(0);
|
||||||
|
int eq = buffer.indexOf('=');
|
||||||
|
if (eq < 0) {
|
||||||
|
commands.complete(reader, commandLine, candidates);
|
||||||
|
} else if (reader.getParser().validVariableName(buffer.substring(0, eq))) {
|
||||||
|
String curBuf = buffer.substring(0, eq + 1);
|
||||||
|
for (String c : completers.keySet()) {
|
||||||
|
candidates.add(
|
||||||
|
new Candidate(AttributedString.stripAnsi(curBuf + c), c, null, null, null, null, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String cmd = reader.getParser().getCommand(commandLine.words().get(0));
|
||||||
|
if (command(cmd) != null) {
|
||||||
|
completers.get(command(cmd)).get(0).complete(reader, commandLine, candidates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCompiled() {
|
||||||
|
return compiled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String command(String cmd) {
|
||||||
|
String out = null;
|
||||||
|
if (cmd != null) {
|
||||||
|
if (completers.containsKey(cmd)) {
|
||||||
|
out = cmd;
|
||||||
|
} else {
|
||||||
|
out = aliasCommand.get(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String command, List<Completer> completers) {
|
||||||
|
for (Completer c : completers) {
|
||||||
|
add(command, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(List<String> commands, Completer completer) {
|
||||||
|
for (String c : commands) {
|
||||||
|
add(c, completer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String command, Completer completer) {
|
||||||
|
Objects.requireNonNull(command);
|
||||||
|
if (compiled) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (!completers.containsKey(command)) {
|
||||||
|
completers.put(command, new ArrayList<Completer>());
|
||||||
|
}
|
||||||
|
if (completer instanceof ArgumentCompleter) {
|
||||||
|
((ArgumentCompleter) completer).setStrictCommand(false);
|
||||||
|
}
|
||||||
|
completers.get(command).add(completer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(SystemCompleter other) {
|
||||||
|
if (other.isCompiled()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, List<Completer>> entry : other.getCompleters().entrySet()) {
|
||||||
|
for (Completer c : entry.getValue()) {
|
||||||
|
add(entry.getKey(), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addAliases(other.getAliases());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAliases(Map<String, String> aliasCommand) {
|
||||||
|
if (compiled) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
this.aliasCommand.putAll(aliasCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> getAliases() {
|
||||||
|
return aliasCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compile() {
|
||||||
|
if (compiled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, List<Completer>> compiledCompleters = new HashMap<>();
|
||||||
|
for (Map.Entry<String, List<Completer>> entry : completers.entrySet()) {
|
||||||
|
if (entry.getValue().size() == 1) {
|
||||||
|
compiledCompleters.put(entry.getKey(), entry.getValue());
|
||||||
|
} else {
|
||||||
|
compiledCompleters.put(entry.getKey(), new ArrayList<Completer>());
|
||||||
|
compiledCompleters.get(entry.getKey()).add(new AggregateCompleter(entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completers = compiledCompleters;
|
||||||
|
Set<String> cmds = new HashSet<>(completers.keySet());
|
||||||
|
cmds.addAll(aliasCommand.keySet());
|
||||||
|
commands = new StringsCompleter(cmds);
|
||||||
|
compiled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<Completer>> getCompleters() {
|
||||||
|
return completers;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author or authors.
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* JLine 3.
|
||||||
|
*
|
||||||
|
* @since 3.0
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.completer;
|
|
@ -0,0 +1,691 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.history;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.time.DateTimeException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import org.jline.reader.History;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
import static org.jline.reader.LineReader.HISTORY_IGNORE;
|
||||||
|
import static org.jline.reader.impl.ReaderUtils.getBoolean;
|
||||||
|
import static org.jline.reader.impl.ReaderUtils.getInt;
|
||||||
|
import static org.jline.reader.impl.ReaderUtils.getString;
|
||||||
|
import static org.jline.reader.impl.ReaderUtils.isSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link History} using a file for persistent backing.
|
||||||
|
* <p>
|
||||||
|
* Implementers should install shutdown hook to call {@link DefaultHistory#save}
|
||||||
|
* to save history to disk.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public class DefaultHistory implements History {
|
||||||
|
|
||||||
|
public static final int DEFAULT_HISTORY_SIZE = 500;
|
||||||
|
public static final int DEFAULT_HISTORY_FILE_SIZE = 10000;
|
||||||
|
|
||||||
|
private final LinkedList<Entry> items = new LinkedList<>();
|
||||||
|
|
||||||
|
private LineReader reader;
|
||||||
|
|
||||||
|
private Map<String, HistoryFileData> historyFiles = new HashMap<>();
|
||||||
|
private int offset = 0;
|
||||||
|
private int index = 0;
|
||||||
|
|
||||||
|
public DefaultHistory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public DefaultHistory(LineReader reader) {
|
||||||
|
attach(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Entry> doTrimHistory(List<Entry> allItems, int max) {
|
||||||
|
int idx = 0;
|
||||||
|
while (idx < allItems.size()) {
|
||||||
|
int ridx = allItems.size() - idx - 1;
|
||||||
|
String line = allItems.get(ridx).line().trim();
|
||||||
|
ListIterator<Entry> iterator = allItems.listIterator(ridx);
|
||||||
|
while (iterator.hasPrevious()) {
|
||||||
|
String l = iterator.previous().line();
|
||||||
|
if (line.equals(l.trim())) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
while (allItems.size() > max) {
|
||||||
|
allItems.remove(0);
|
||||||
|
}
|
||||||
|
int index = allItems.get(allItems.size() - 1).index() - allItems.size() + 1;
|
||||||
|
List<Entry> out = new ArrayList<>();
|
||||||
|
for (Entry e : allItems) {
|
||||||
|
out.add(new EntryImpl(index++, e.time(), e.line()));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escape(String s) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
char ch = s.charAt(i);
|
||||||
|
switch (ch) {
|
||||||
|
case '\n':
|
||||||
|
sb.append('\\');
|
||||||
|
sb.append('n');
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
sb.append('\\');
|
||||||
|
sb.append('r');
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
sb.append('\\');
|
||||||
|
sb.append('\\');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(ch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String unescape(String s) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
char ch = s.charAt(i);
|
||||||
|
switch (ch) {
|
||||||
|
case '\\':
|
||||||
|
ch = s.charAt(++i);
|
||||||
|
if (ch == 'n') {
|
||||||
|
sb.append('\n');
|
||||||
|
} else if (ch == 'r') {
|
||||||
|
sb.append('\r');
|
||||||
|
} else {
|
||||||
|
sb.append(ch);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(ch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path getPath() {
|
||||||
|
Object obj = reader != null ? reader.getVariables().get(LineReader.HISTORY_FILE) : null;
|
||||||
|
if (obj instanceof Path) {
|
||||||
|
return (Path) obj;
|
||||||
|
} else if (obj instanceof File) {
|
||||||
|
return ((File) obj).toPath();
|
||||||
|
} else if (obj != null) {
|
||||||
|
return Paths.get(obj.toString());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attach(LineReader reader) {
|
||||||
|
if (this.reader != reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
try {
|
||||||
|
load();
|
||||||
|
} catch (IllegalArgumentException | IOException e) {
|
||||||
|
Log.warn("Failed to load history", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load() throws IOException {
|
||||||
|
Path path = getPath();
|
||||||
|
if (path != null) {
|
||||||
|
try {
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
Log.trace("Loading history from: ", path);
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(path)) {
|
||||||
|
internalClear();
|
||||||
|
reader.lines().forEach(line -> addHistoryLine(path, line));
|
||||||
|
setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size()));
|
||||||
|
maybeResize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException | IOException e) {
|
||||||
|
Log.debug("Failed to load history; clearing", e);
|
||||||
|
internalClear();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(Path file, boolean checkDuplicates) throws IOException {
|
||||||
|
Path path = file != null ? file : getPath();
|
||||||
|
if (path != null) {
|
||||||
|
try {
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
Log.trace("Reading history from: ", path);
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(path)) {
|
||||||
|
reader.lines().forEach(line -> addHistoryLine(path, line, checkDuplicates));
|
||||||
|
setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size()));
|
||||||
|
maybeResize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException | IOException e) {
|
||||||
|
Log.debug("Failed to read history; clearing", e);
|
||||||
|
internalClear();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String doHistoryFileDataKey(Path path) {
|
||||||
|
return path != null ? path.toAbsolutePath().toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HistoryFileData getHistoryFileData(Path path) {
|
||||||
|
String key = doHistoryFileDataKey(path);
|
||||||
|
if (!historyFiles.containsKey(key)) {
|
||||||
|
historyFiles.put(key, new HistoryFileData());
|
||||||
|
}
|
||||||
|
return historyFiles.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHistoryFileData(Path path, HistoryFileData historyFileData) {
|
||||||
|
historyFiles.put(doHistoryFileDataKey(path), historyFileData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLineReaderHistory(Path path) throws IOException {
|
||||||
|
Path lrp = getPath();
|
||||||
|
if (lrp == null) {
|
||||||
|
return path == null;
|
||||||
|
}
|
||||||
|
return Files.isSameFile(lrp, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLastLoaded(Path path, int lastloaded) {
|
||||||
|
getHistoryFileData(path).setLastLoaded(lastloaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setEntriesInFile(Path path, int entriesInFile) {
|
||||||
|
getHistoryFileData(path).setEntriesInFile(entriesInFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incEntriesInFile(Path path, int amount) {
|
||||||
|
getHistoryFileData(path).incEntriesInFile(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLastLoaded(Path path) {
|
||||||
|
return getHistoryFileData(path).getLastLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getEntriesInFile(Path path) {
|
||||||
|
return getHistoryFileData(path).getEntriesInFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addHistoryLine(Path path, String line) {
|
||||||
|
addHistoryLine(path, line, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addHistoryLine(Path path, String line, boolean checkDuplicates) {
|
||||||
|
if (reader.isSet(LineReader.Option.HISTORY_TIMESTAMPED)) {
|
||||||
|
int idx = line.indexOf(':');
|
||||||
|
final String badHistoryFileSyntax = "Bad history file syntax! " + "The history file `" + path
|
||||||
|
+ "` may be an older history: " + "please remove it or use a different history file.";
|
||||||
|
if (idx < 0) {
|
||||||
|
throw new IllegalArgumentException(badHistoryFileSyntax);
|
||||||
|
}
|
||||||
|
Instant time;
|
||||||
|
try {
|
||||||
|
time = Instant.ofEpochMilli(Long.parseLong(line.substring(0, idx)));
|
||||||
|
} catch (DateTimeException | NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException(badHistoryFileSyntax);
|
||||||
|
}
|
||||||
|
|
||||||
|
String unescaped = unescape(line.substring(idx + 1));
|
||||||
|
internalAdd(time, unescaped, checkDuplicates);
|
||||||
|
} else {
|
||||||
|
internalAdd(Instant.now(), unescape(line), checkDuplicates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void purge() throws IOException {
|
||||||
|
internalClear();
|
||||||
|
Path path = getPath();
|
||||||
|
if (path != null) {
|
||||||
|
Log.trace("Purging history from: ", path);
|
||||||
|
Files.deleteIfExists(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(Path file, boolean incremental) throws IOException {
|
||||||
|
Path path = file != null ? file : getPath();
|
||||||
|
if (path != null && Files.exists(path)) {
|
||||||
|
path.toFile().delete();
|
||||||
|
}
|
||||||
|
internalWrite(path, incremental ? getLastLoaded(path) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(Path file, boolean incremental) throws IOException {
|
||||||
|
internalWrite(file != null ? file : getPath(), incremental ? getLastLoaded(file) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save() throws IOException {
|
||||||
|
internalWrite(getPath(), getLastLoaded(getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalWrite(Path path, int from) throws IOException {
|
||||||
|
if (path != null) {
|
||||||
|
Log.trace("Saving history to: ", path);
|
||||||
|
Path parent = path.toAbsolutePath().getParent();
|
||||||
|
if (!Files.exists(parent)) {
|
||||||
|
Files.createDirectories(parent);
|
||||||
|
}
|
||||||
|
// Append new items to the history file
|
||||||
|
try (BufferedWriter writer = Files.newBufferedWriter(
|
||||||
|
path.toAbsolutePath(),
|
||||||
|
StandardOpenOption.WRITE,
|
||||||
|
StandardOpenOption.APPEND,
|
||||||
|
StandardOpenOption.CREATE)) {
|
||||||
|
for (Entry entry : items.subList(from, items.size())) {
|
||||||
|
if (isPersistable(entry)) {
|
||||||
|
writer.append(format(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
incEntriesInFile(path, items.size() - from);
|
||||||
|
int max = getInt(reader, LineReader.HISTORY_FILE_SIZE, DEFAULT_HISTORY_FILE_SIZE);
|
||||||
|
if (getEntriesInFile(path) > max + max / 4) {
|
||||||
|
trimHistory(path, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLastLoaded(path, items.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void trimHistory(Path path, int max) throws IOException {
|
||||||
|
Log.trace("Trimming history path: ", path);
|
||||||
|
// Load all history entries
|
||||||
|
LinkedList<Entry> allItems = new LinkedList<>();
|
||||||
|
try (BufferedReader historyFileReader = Files.newBufferedReader(path)) {
|
||||||
|
historyFileReader.lines().forEach(l -> {
|
||||||
|
if (reader.isSet(LineReader.Option.HISTORY_TIMESTAMPED)) {
|
||||||
|
int idx = l.indexOf(':');
|
||||||
|
Instant time = Instant.ofEpochMilli(Long.parseLong(l.substring(0, idx)));
|
||||||
|
String line = unescape(l.substring(idx + 1));
|
||||||
|
allItems.add(createEntry(allItems.size(), time, line));
|
||||||
|
} else {
|
||||||
|
allItems.add(createEntry(allItems.size(), Instant.now(), unescape(l)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Remove duplicates
|
||||||
|
List<Entry> trimmedItems = doTrimHistory(allItems, max);
|
||||||
|
// Write history
|
||||||
|
Path temp = Files.createTempFile(
|
||||||
|
path.toAbsolutePath().getParent(), path.getFileName().toString(), ".tmp");
|
||||||
|
try (BufferedWriter writer = Files.newBufferedWriter(temp, StandardOpenOption.WRITE)) {
|
||||||
|
for (Entry entry : trimmedItems) {
|
||||||
|
writer.append(format(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Files.move(temp, path, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
// Keep items in memory
|
||||||
|
if (isLineReaderHistory(path)) {
|
||||||
|
internalClear();
|
||||||
|
offset = trimmedItems.get(0).index();
|
||||||
|
items.addAll(trimmedItems);
|
||||||
|
setHistoryFileData(path, new HistoryFileData(items.size(), items.size()));
|
||||||
|
} else {
|
||||||
|
setEntriesInFile(path, allItems.size());
|
||||||
|
}
|
||||||
|
maybeResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a history entry. Subclasses may override to use their own entry implementations.
|
||||||
|
*
|
||||||
|
* @param index index of history entry
|
||||||
|
* @param time entry creation time
|
||||||
|
* @param line the entry text
|
||||||
|
* @return entry object
|
||||||
|
*/
|
||||||
|
protected EntryImpl createEntry(int index, Instant time, String line) {
|
||||||
|
return new EntryImpl(index, time, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalClear() {
|
||||||
|
offset = 0;
|
||||||
|
index = 0;
|
||||||
|
historyFiles = new HashMap<>();
|
||||||
|
items.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return items.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int index() {
|
||||||
|
return offset + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int first() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int last() {
|
||||||
|
return offset + items.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String format(Entry entry) {
|
||||||
|
if (reader.isSet(LineReader.Option.HISTORY_TIMESTAMPED)) {
|
||||||
|
return entry.time().toEpochMilli() + ":" + escape(entry.line()) + "\n";
|
||||||
|
}
|
||||||
|
return escape(entry.line()) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(final int index) {
|
||||||
|
int idx = index - offset;
|
||||||
|
if (idx >= items.size() || idx < 0) {
|
||||||
|
throw new IllegalArgumentException("IndexOutOfBounds: Index:" + idx + ", Size:" + items.size());
|
||||||
|
}
|
||||||
|
return items.get(idx).line();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Instant time, String line) {
|
||||||
|
Objects.requireNonNull(time);
|
||||||
|
Objects.requireNonNull(line);
|
||||||
|
|
||||||
|
if (getBoolean(reader, LineReader.DISABLE_HISTORY, false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isSet(reader, LineReader.Option.HISTORY_IGNORE_SPACE) && line.startsWith(" ")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isSet(reader, LineReader.Option.HISTORY_REDUCE_BLANKS)) {
|
||||||
|
line = line.trim();
|
||||||
|
}
|
||||||
|
if (isSet(reader, LineReader.Option.HISTORY_IGNORE_DUPS)) {
|
||||||
|
if (!items.isEmpty() && line.equals(items.getLast().line())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchPatterns(getString(reader, HISTORY_IGNORE, ""), line)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internalAdd(time, line);
|
||||||
|
if (isSet(reader, LineReader.Option.HISTORY_INCREMENTAL)) {
|
||||||
|
try {
|
||||||
|
save();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.warn("Failed to save history", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean matchPatterns(String patterns, String line) {
|
||||||
|
if (patterns == null || patterns.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < patterns.length(); i++) {
|
||||||
|
char ch = patterns.charAt(i);
|
||||||
|
if (ch == '\\') {
|
||||||
|
ch = patterns.charAt(++i);
|
||||||
|
sb.append(ch);
|
||||||
|
} else if (ch == ':') {
|
||||||
|
sb.append('|');
|
||||||
|
} else if (ch == '*') {
|
||||||
|
sb.append('.').append('*');
|
||||||
|
} else {
|
||||||
|
sb.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return line.matches(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void internalAdd(Instant time, String line) {
|
||||||
|
internalAdd(time, line, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void internalAdd(Instant time, String line, boolean checkDuplicates) {
|
||||||
|
Entry entry = new EntryImpl(offset + items.size(), time, line);
|
||||||
|
if (checkDuplicates) {
|
||||||
|
for (Entry e : items) {
|
||||||
|
if (e.line().trim().equals(line.trim())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.add(entry);
|
||||||
|
maybeResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeResize() {
|
||||||
|
while (size() > getInt(reader, LineReader.HISTORY_SIZE, DEFAULT_HISTORY_SIZE)) {
|
||||||
|
items.removeFirst();
|
||||||
|
for (HistoryFileData hfd : historyFiles.values()) {
|
||||||
|
hfd.decLastLoaded();
|
||||||
|
}
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
index = size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListIterator<Entry> iterator(int index) {
|
||||||
|
return items.listIterator(index - offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Spliterator<Entry> spliterator() {
|
||||||
|
return items.spliterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Navigation
|
||||||
|
//
|
||||||
|
|
||||||
|
public void resetIndex() {
|
||||||
|
index = Math.min(index, items.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This moves the history to the last entry. This entry is one position
|
||||||
|
* before the moveToEnd() position.
|
||||||
|
*
|
||||||
|
* @return Returns false if there were no history iterator or the history
|
||||||
|
* index was already at the last entry.
|
||||||
|
*/
|
||||||
|
public boolean moveToLast() {
|
||||||
|
int lastEntry = size() - 1;
|
||||||
|
if (lastEntry >= 0 && lastEntry != index) {
|
||||||
|
index = size() - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to the specified index in the history
|
||||||
|
*/
|
||||||
|
public boolean moveTo(int index) {
|
||||||
|
index -= offset;
|
||||||
|
if (index >= 0 && index < size()) {
|
||||||
|
this.index = index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the history index to the first entry.
|
||||||
|
*
|
||||||
|
* @return Return false if there are no iterator in the history or if the
|
||||||
|
* history is already at the beginning.
|
||||||
|
*/
|
||||||
|
public boolean moveToFirst() {
|
||||||
|
if (size() > 0 && index != 0) {
|
||||||
|
index = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to the end of the history buffer. This will be a blank entry, after
|
||||||
|
* all of the other iterator.
|
||||||
|
*/
|
||||||
|
public void moveToEnd() {
|
||||||
|
index = size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the content of the current buffer.
|
||||||
|
*/
|
||||||
|
public String current() {
|
||||||
|
if (index >= size()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return items.get(index).line();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the pointer to the previous element in the buffer.
|
||||||
|
*
|
||||||
|
* @return true if we successfully went to the previous element
|
||||||
|
*/
|
||||||
|
public boolean previous() {
|
||||||
|
if (index <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
index--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the pointer to the next element in the buffer.
|
||||||
|
*
|
||||||
|
* @return true if we successfully went to the next element
|
||||||
|
*/
|
||||||
|
public boolean next() {
|
||||||
|
if (index >= size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Entry e : this) {
|
||||||
|
sb.append(e.toString()).append("\n");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class EntryImpl implements Entry {
|
||||||
|
|
||||||
|
private final int index;
|
||||||
|
private final Instant time;
|
||||||
|
private final String line;
|
||||||
|
|
||||||
|
public EntryImpl(int index, Instant time, String line) {
|
||||||
|
this.index = index;
|
||||||
|
this.time = time;
|
||||||
|
this.line = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int index() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant time() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String line() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%d: %s", index, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HistoryFileData {
|
||||||
|
private int lastLoaded = 0;
|
||||||
|
private int entriesInFile = 0;
|
||||||
|
|
||||||
|
public HistoryFileData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public HistoryFileData(int lastLoaded, int entriesInFile) {
|
||||||
|
this.lastLoaded = lastLoaded;
|
||||||
|
this.entriesInFile = entriesInFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastLoaded() {
|
||||||
|
return lastLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastLoaded(int lastLoaded) {
|
||||||
|
this.lastLoaded = lastLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decLastLoaded() {
|
||||||
|
lastLoaded = lastLoaded - 1;
|
||||||
|
if (lastLoaded < 0) {
|
||||||
|
lastLoaded = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEntriesInFile() {
|
||||||
|
return entriesInFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntriesInFile(int entriesInFile) {
|
||||||
|
this.entriesInFile = entriesInFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incEntriesInFile(int amount) {
|
||||||
|
entriesInFile = entriesInFile + amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author or authors.
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* JLine 3.
|
||||||
|
*
|
||||||
|
* @since 3.0
|
||||||
|
*/
|
||||||
|
package org.jline.reader.impl.history;
|
349
net-cli/src/main/java/org/jline/terminal/Attributes.java
Normal file
349
net-cli/src/main/java/org/jline/terminal/Attributes.java
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class Attributes {
|
||||||
|
|
||||||
|
final EnumSet<InputFlag> iflag = EnumSet.noneOf(InputFlag.class);
|
||||||
|
final EnumSet<OutputFlag> oflag = EnumSet.noneOf(OutputFlag.class);
|
||||||
|
final EnumSet<ControlFlag> cflag = EnumSet.noneOf(ControlFlag.class);
|
||||||
|
final EnumSet<LocalFlag> lflag = EnumSet.noneOf(LocalFlag.class);
|
||||||
|
final EnumMap<ControlChar, Integer> cchars = new EnumMap<>(ControlChar.class);
|
||||||
|
|
||||||
|
public Attributes() {
|
||||||
|
}
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public Attributes(Attributes attr) {
|
||||||
|
copy(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnumSet<InputFlag> getInputFlags() {
|
||||||
|
return iflag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputFlags(EnumSet<InputFlag> flags) {
|
||||||
|
iflag.clear();
|
||||||
|
iflag.addAll(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getInputFlag(InputFlag flag) {
|
||||||
|
return iflag.contains(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputFlags(EnumSet<InputFlag> flags, boolean value) {
|
||||||
|
if (value) {
|
||||||
|
iflag.addAll(flags);
|
||||||
|
} else {
|
||||||
|
iflag.removeAll(flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputFlag(InputFlag flag, boolean value) {
|
||||||
|
if (value) {
|
||||||
|
iflag.add(flag);
|
||||||
|
} else {
|
||||||
|
iflag.remove(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Input flags
|
||||||
|
//
|
||||||
|
|
||||||
|
public EnumSet<OutputFlag> getOutputFlags() {
|
||||||
|
return oflag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputFlags(EnumSet<OutputFlag> flags) {
|
||||||
|
oflag.clear();
|
||||||
|
oflag.addAll(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getOutputFlag(OutputFlag flag) {
|
||||||
|
return oflag.contains(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputFlags(EnumSet<OutputFlag> flags, boolean value) {
|
||||||
|
if (value) {
|
||||||
|
oflag.addAll(flags);
|
||||||
|
} else {
|
||||||
|
oflag.removeAll(flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputFlag(OutputFlag flag, boolean value) {
|
||||||
|
if (value) {
|
||||||
|
oflag.add(flag);
|
||||||
|
} else {
|
||||||
|
oflag.remove(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Output flags
|
||||||
|
//
|
||||||
|
|
||||||
|
public EnumSet<ControlFlag> getControlFlags() {
|
||||||
|
return cflag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setControlFlags(EnumSet<ControlFlag> flags) {
|
||||||
|
cflag.clear();
|
||||||
|
cflag.addAll(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getControlFlag(ControlFlag flag) {
|
||||||
|
return cflag.contains(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setControlFlags(EnumSet<ControlFlag> flags, boolean value) {
|
||||||
|
if (value) {
|
||||||
|
cflag.addAll(flags);
|
||||||
|
} else {
|
||||||
|
cflag.removeAll(flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setControlFlag(ControlFlag flag, boolean value) {
|
||||||
|
if (value) {
|
||||||
|
cflag.add(flag);
|
||||||
|
} else {
|
||||||
|
cflag.remove(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Control flags
|
||||||
|
//
|
||||||
|
|
||||||
|
public EnumSet<LocalFlag> getLocalFlags() {
|
||||||
|
return lflag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalFlags(EnumSet<LocalFlag> flags) {
|
||||||
|
lflag.clear();
|
||||||
|
lflag.addAll(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getLocalFlag(LocalFlag flag) {
|
||||||
|
return lflag.contains(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalFlags(EnumSet<LocalFlag> flags, boolean value) {
|
||||||
|
if (value) {
|
||||||
|
lflag.addAll(flags);
|
||||||
|
} else {
|
||||||
|
lflag.removeAll(flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalFlag(LocalFlag flag, boolean value) {
|
||||||
|
if (value) {
|
||||||
|
lflag.add(flag);
|
||||||
|
} else {
|
||||||
|
lflag.remove(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Local flags
|
||||||
|
//
|
||||||
|
|
||||||
|
public EnumMap<ControlChar, Integer> getControlChars() {
|
||||||
|
return cchars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setControlChars(EnumMap<ControlChar, Integer> chars) {
|
||||||
|
cchars.clear();
|
||||||
|
cchars.putAll(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getControlChar(ControlChar c) {
|
||||||
|
Integer v = cchars.get(c);
|
||||||
|
return v != null ? v : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setControlChar(ControlChar c, int value) {
|
||||||
|
cchars.put(c, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copy(Attributes attributes) {
|
||||||
|
setControlFlags(attributes.getControlFlags());
|
||||||
|
setInputFlags(attributes.getInputFlags());
|
||||||
|
setLocalFlags(attributes.getLocalFlags());
|
||||||
|
setOutputFlags(attributes.getOutputFlags());
|
||||||
|
setControlChars(attributes.getControlChars());
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Control chars
|
||||||
|
//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Attributes[" + "lflags: "
|
||||||
|
+ append(lflag) + ", " + "iflags: "
|
||||||
|
+ append(iflag) + ", " + "oflags: "
|
||||||
|
+ append(oflag) + ", " + "cflags: "
|
||||||
|
+ append(cflag) + ", " + "cchars: "
|
||||||
|
+ append(EnumSet.allOf(ControlChar.class), this::display) + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String display(ControlChar c) {
|
||||||
|
String value;
|
||||||
|
int ch = getControlChar(c);
|
||||||
|
if (c == ControlChar.VMIN || c == ControlChar.VTIME) {
|
||||||
|
value = Integer.toString(ch);
|
||||||
|
} else if (ch < 0) {
|
||||||
|
value = "<undef>";
|
||||||
|
} else if (ch < 32) {
|
||||||
|
value = "^" + (char) (ch + 'A' - 1);
|
||||||
|
} else if (ch == 127) {
|
||||||
|
value = "^?";
|
||||||
|
} else if (ch >= 128) {
|
||||||
|
value = String.format("\\u%04x", ch);
|
||||||
|
} else {
|
||||||
|
value = String.valueOf((char) ch);
|
||||||
|
}
|
||||||
|
return c.name().toLowerCase().substring(1) + "=" + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends Enum<T>> String append(EnumSet<T> set) {
|
||||||
|
return append(set, e -> e.name().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends Enum<T>> String append(EnumSet<T> set, Function<T, String> toString) {
|
||||||
|
return set.stream().map(toString).collect(Collectors.joining(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Miscellaneous methods
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control characters
|
||||||
|
*/
|
||||||
|
public enum ControlChar {
|
||||||
|
VEOF,
|
||||||
|
VEOL,
|
||||||
|
VEOL2,
|
||||||
|
VERASE,
|
||||||
|
VWERASE,
|
||||||
|
VKILL,
|
||||||
|
VREPRINT,
|
||||||
|
VINTR,
|
||||||
|
VQUIT,
|
||||||
|
VSUSP,
|
||||||
|
VDSUSP,
|
||||||
|
VSTART,
|
||||||
|
VSTOP,
|
||||||
|
VLNEXT,
|
||||||
|
VDISCARD,
|
||||||
|
VMIN,
|
||||||
|
VTIME,
|
||||||
|
VSTATUS
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input flags - software input processing
|
||||||
|
*/
|
||||||
|
public enum InputFlag {
|
||||||
|
IGNBRK, /* ignore BREAK condition */
|
||||||
|
BRKINT, /* map BREAK to SIGINTR */
|
||||||
|
IGNPAR, /* ignore (discard) parity errors */
|
||||||
|
PARMRK, /* mark parity and framing errors */
|
||||||
|
INPCK, /* enable checking of parity errors */
|
||||||
|
ISTRIP, /* strip 8th bit off chars */
|
||||||
|
INLCR, /* map NL into CR */
|
||||||
|
IGNCR, /* ignore CR */
|
||||||
|
ICRNL, /* map CR to NL (ala CRMOD) */
|
||||||
|
IXON, /* enable output flow control */
|
||||||
|
IXOFF, /* enable input flow control */
|
||||||
|
IXANY, /* any char will restart after stop */
|
||||||
|
IMAXBEL, /* ring bell on input queue full */
|
||||||
|
IUTF8, /* maintain state for UTF-8 VERASE */
|
||||||
|
|
||||||
|
INORMEOL /* normalize end-of-line */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Output flags - software output processing
|
||||||
|
*/
|
||||||
|
public enum OutputFlag {
|
||||||
|
OPOST, /* enable following output processing */
|
||||||
|
ONLCR, /* map NL to CR-NL (ala CRMOD) */
|
||||||
|
OXTABS, /* expand tabs to spaces */
|
||||||
|
ONOEOT, /* discard EOT's (^D) on output) */
|
||||||
|
OCRNL, /* map CR to NL on output */
|
||||||
|
ONOCR, /* no CR output at column 0 */
|
||||||
|
ONLRET, /* NL performs CR function */
|
||||||
|
OFILL, /* use fill characters for delay */
|
||||||
|
NLDLY, /* \n delay */
|
||||||
|
TABDLY, /* horizontal tab delay */
|
||||||
|
CRDLY, /* \r delay */
|
||||||
|
FFDLY, /* form feed delay */
|
||||||
|
BSDLY, /* \b delay */
|
||||||
|
VTDLY, /* vertical tab delay */
|
||||||
|
OFDEL /* fill is DEL, else NUL */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Control flags - hardware control of terminal
|
||||||
|
*/
|
||||||
|
public enum ControlFlag {
|
||||||
|
CIGNORE, /* ignore control flags */
|
||||||
|
CS5, /* 5 bits (pseudo) */
|
||||||
|
CS6, /* 6 bits */
|
||||||
|
CS7, /* 7 bits */
|
||||||
|
CS8, /* 8 bits */
|
||||||
|
CSTOPB, /* send 2 stop bits */
|
||||||
|
CREAD, /* enable receiver */
|
||||||
|
PARENB, /* parity enable */
|
||||||
|
PARODD, /* odd parity, else even */
|
||||||
|
HUPCL, /* hang up on last close */
|
||||||
|
CLOCAL, /* ignore modem status lines */
|
||||||
|
CCTS_OFLOW, /* CTS flow control of output */
|
||||||
|
CRTS_IFLOW, /* RTS flow control of input */
|
||||||
|
CDTR_IFLOW, /* DTR flow control of input */
|
||||||
|
CDSR_OFLOW, /* DSR flow control of output */
|
||||||
|
CCAR_OFLOW /* DCD flow control of output */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Local" flags - dumping ground for other state
|
||||||
|
*
|
||||||
|
* Warning: some flags in this structure begin with
|
||||||
|
* the letter "I" and look like they belong in the
|
||||||
|
* input flag.
|
||||||
|
*/
|
||||||
|
public enum LocalFlag {
|
||||||
|
ECHOKE, /* visual erase for line kill */
|
||||||
|
ECHOE, /* visually erase chars */
|
||||||
|
ECHOK, /* echo NL after line kill */
|
||||||
|
ECHO, /* enable echoing */
|
||||||
|
ECHONL, /* echo NL even if ECHO is off */
|
||||||
|
ECHOPRT, /* visual erase mode for hardcopy */
|
||||||
|
ECHOCTL, /* echo control chars as ^(Char) */
|
||||||
|
ISIG, /* enable signals INTR, QUIT, [D]SUSP */
|
||||||
|
ICANON, /* canonicalize input lines */
|
||||||
|
ALTWERASE, /* use alternate WERASE algorithm */
|
||||||
|
IEXTEN, /* enable DISCARD and LNEXT */
|
||||||
|
EXTPROC, /* external processing */
|
||||||
|
TOSTOP, /* stop background jobs from output */
|
||||||
|
FLUSHO, /* output being flushed (state) */
|
||||||
|
NOKERNINFO, /* no kernel output from VSTATUS */
|
||||||
|
PENDIN, /* XXX retype pending input (state) */
|
||||||
|
NOFLSH /* don't flush after interrupt */
|
||||||
|
}
|
||||||
|
}
|
52
net-cli/src/main/java/org/jline/terminal/Cursor.java
Normal file
52
net-cli/src/main/java/org/jline/terminal/Cursor.java
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class holding the cursor position.
|
||||||
|
*
|
||||||
|
* @see Terminal#getCursorPosition(java.util.function.IntConsumer)
|
||||||
|
*/
|
||||||
|
public class Cursor {
|
||||||
|
|
||||||
|
private final int x;
|
||||||
|
private final int y;
|
||||||
|
|
||||||
|
public Cursor(int x, int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof Cursor c) {
|
||||||
|
return x == c.x && y == c.y;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return x * 31 + y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Cursor[" + "x=" + x + ", y=" + y + ']';
|
||||||
|
}
|
||||||
|
}
|
80
net-cli/src/main/java/org/jline/terminal/MouseEvent.java
Normal file
80
net-cli/src/main/java/org/jline/terminal/MouseEvent.java
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
public class MouseEvent {
|
||||||
|
|
||||||
|
private final Type type;
|
||||||
|
private final Button button;
|
||||||
|
private final EnumSet<Modifier> modifiers;
|
||||||
|
private final int x;
|
||||||
|
private final int y;
|
||||||
|
public MouseEvent(Type type, Button button, EnumSet<Modifier> modifiers, int x, int y) {
|
||||||
|
this.type = type;
|
||||||
|
this.button = button;
|
||||||
|
this.modifiers = modifiers;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Button getButton() {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnumSet<Modifier> getModifiers() {
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MouseEvent[" + "type="
|
||||||
|
+ type + ", button="
|
||||||
|
+ button + ", modifiers="
|
||||||
|
+ modifiers + ", x="
|
||||||
|
+ x + ", y="
|
||||||
|
+ y + ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
Released,
|
||||||
|
Pressed,
|
||||||
|
Wheel,
|
||||||
|
Moved,
|
||||||
|
Dragged
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Button {
|
||||||
|
NoButton,
|
||||||
|
Button1,
|
||||||
|
Button2,
|
||||||
|
Button3,
|
||||||
|
WheelUp,
|
||||||
|
WheelDown
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Modifier {
|
||||||
|
Shift,
|
||||||
|
Alt,
|
||||||
|
Control
|
||||||
|
}
|
||||||
|
}
|
80
net-cli/src/main/java/org/jline/terminal/Size.java
Normal file
80
net-cli/src/main/java/org/jline/terminal/Size.java
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal;
|
||||||
|
|
||||||
|
public class Size {
|
||||||
|
|
||||||
|
private int rows;
|
||||||
|
private int cols;
|
||||||
|
|
||||||
|
public Size() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public Size(int columns, int rows) {
|
||||||
|
this();
|
||||||
|
setColumns(columns);
|
||||||
|
setRows(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColumns() {
|
||||||
|
return cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumns(int columns) {
|
||||||
|
cols = (short) columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRows() {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRows(int rows) {
|
||||||
|
this.rows = (short) rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cursor position combines a row number with a column position.
|
||||||
|
* <p>
|
||||||
|
* Note each row has {@code col+1} different column positions,
|
||||||
|
* including the right margin.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param col the new column
|
||||||
|
* @param row the new row
|
||||||
|
* @return the cursor position
|
||||||
|
*/
|
||||||
|
public int cursorPos(int row, int col) {
|
||||||
|
return row * (cols + 1) + col;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copy(Size size) {
|
||||||
|
setColumns(size.getColumns());
|
||||||
|
setRows(size.getRows());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof Size size) {
|
||||||
|
return rows == size.rows && cols == size.cols;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return rows * 31 + cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Size[" + "cols=" + cols + ", rows=" + rows + ']';
|
||||||
|
}
|
||||||
|
}
|
396
net-cli/src/main/java/org/jline/terminal/Terminal.java
Normal file
396
net-cli/src/main/java/org/jline/terminal/Terminal.java
Normal file
|
@ -0,0 +1,396 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.Flushable;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
|
import org.jline.terminal.impl.NativeSignalHandler;
|
||||||
|
import org.jline.utils.ColorPalette;
|
||||||
|
import org.jline.utils.InfoCmp.Capability;
|
||||||
|
import org.jline.utils.NonBlockingReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A terminal representing a virtual terminal on the computer.
|
||||||
|
* <p>
|
||||||
|
* Terminals should be closed by calling the {@link #close()} method
|
||||||
|
* in order to restore their original state.
|
||||||
|
*/
|
||||||
|
public interface Terminal extends Closeable, Flushable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type used for dumb terminals.
|
||||||
|
*/
|
||||||
|
String TYPE_DUMB = "dumb";
|
||||||
|
|
||||||
|
String TYPE_DUMB_COLOR = "dumb-color";
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Signal support
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a handler for the given {@link Signal}.
|
||||||
|
* <p>
|
||||||
|
* Note that the JVM does not easily allow catching the {@link Signal#QUIT} signal, which causes a thread dump
|
||||||
|
* to be displayed. This signal is mainly used when connecting through an SSH socket to a virtual terminal.
|
||||||
|
*
|
||||||
|
* @param signal the signal to register a handler for
|
||||||
|
* @param handler the handler
|
||||||
|
* @return the previous signal handler
|
||||||
|
*/
|
||||||
|
SignalHandler handle(Signal signal, SignalHandler handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raise the specific signal.
|
||||||
|
* This is not method usually called by non system terminals.
|
||||||
|
* When accessing a terminal through a SSH or Telnet connection, signals may be
|
||||||
|
* conveyed by the protocol and thus need to be raised when reaching the terminal code.
|
||||||
|
* The terminals do that automatically when the terminal input stream has a character
|
||||||
|
* mapped to {@link Attributes.ControlChar#VINTR}, {@link Attributes.ControlChar#VQUIT},
|
||||||
|
* or {@link Attributes.ControlChar#VSUSP}.
|
||||||
|
*
|
||||||
|
* @param signal the signal to raise
|
||||||
|
*/
|
||||||
|
void raise(Signal signal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the <code>Reader</code> for this terminal.
|
||||||
|
* This is the standard way to read input from this terminal.
|
||||||
|
* The reader is non blocking.
|
||||||
|
*
|
||||||
|
* @return The non blocking reader
|
||||||
|
*/
|
||||||
|
NonBlockingReader reader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the <code>Writer</code> for this terminal.
|
||||||
|
* This is the standard way to write to this terminal.
|
||||||
|
*
|
||||||
|
* @return The writer
|
||||||
|
*/
|
||||||
|
PrintWriter writer();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Input / output
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link Charset} that should be used to encode characters
|
||||||
|
* for {@link #input()} and {@link #output()}.
|
||||||
|
*
|
||||||
|
* @return The terminal encoding
|
||||||
|
*/
|
||||||
|
Charset encoding();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the input stream for this terminal.
|
||||||
|
* In some rare cases, there may be a need to access the
|
||||||
|
* terminal input stream directly. In the usual cases,
|
||||||
|
* use the {@link #reader()} instead.
|
||||||
|
*
|
||||||
|
* @return The input stream
|
||||||
|
* @see #reader()
|
||||||
|
*/
|
||||||
|
InputStream input();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the output stream for this terminal.
|
||||||
|
* In some rare cases, there may be a need to access the
|
||||||
|
* terminal output stream directly. In the usual cases,
|
||||||
|
* use the {@link #writer()} instead.
|
||||||
|
*
|
||||||
|
* @return The output stream
|
||||||
|
* @see #writer()
|
||||||
|
*/
|
||||||
|
OutputStream output();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this terminal supports {@link #pause()} and {@link #resume()} calls.
|
||||||
|
*
|
||||||
|
* @return whether this terminal supports {@link #pause()} and {@link #resume()} calls.
|
||||||
|
* @see #paused()
|
||||||
|
* @see #pause()
|
||||||
|
* @see #resume()
|
||||||
|
*/
|
||||||
|
boolean canPauseResume();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop reading the input stream.
|
||||||
|
*
|
||||||
|
* @see #resume()
|
||||||
|
* @see #paused()
|
||||||
|
*/
|
||||||
|
void pause();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Input control
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop reading the input stream and optionally wait for the underlying threads to finish.
|
||||||
|
*
|
||||||
|
* @param wait <code>true</code> to wait until the terminal is actually paused
|
||||||
|
* @throws InterruptedException if the call has been interrupted
|
||||||
|
*/
|
||||||
|
void pause(boolean wait) throws InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume reading the input stream.
|
||||||
|
*
|
||||||
|
* @see #pause()
|
||||||
|
* @see #paused()
|
||||||
|
*/
|
||||||
|
void resume();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the terminal is currently reading the input stream or not.
|
||||||
|
* In order to process signal as quickly as possible, the terminal need to read
|
||||||
|
* the input stream and buffer it internally so that it can detect specific
|
||||||
|
* characters in the input stream (Ctrl+C, Ctrl+D, etc...) and raise the
|
||||||
|
* appropriate signals.
|
||||||
|
* However, there are some cases where this processing should be disabled, for
|
||||||
|
* example when handing the terminal control to a subprocess.
|
||||||
|
*
|
||||||
|
* @return whether the terminal is currently reading the input stream or not
|
||||||
|
* @see #pause()
|
||||||
|
* @see #resume()
|
||||||
|
*/
|
||||||
|
boolean paused();
|
||||||
|
|
||||||
|
Attributes enterRawMode();
|
||||||
|
|
||||||
|
boolean echo();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Pty settings
|
||||||
|
//
|
||||||
|
|
||||||
|
boolean echo(boolean echo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the terminal attributes.
|
||||||
|
* The returned object can be safely modified
|
||||||
|
* further used in a call to {@link #setAttributes(Attributes)}.
|
||||||
|
*
|
||||||
|
* @return the terminal attributes.
|
||||||
|
*/
|
||||||
|
Attributes getAttributes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the terminal attributes.
|
||||||
|
* The terminal will perform a copy of the given attributes.
|
||||||
|
*
|
||||||
|
* @param attr the new attributes
|
||||||
|
*/
|
||||||
|
void setAttributes(Attributes attr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the size of the visible window
|
||||||
|
*
|
||||||
|
* @return the visible terminal size
|
||||||
|
* @see #getBufferSize()
|
||||||
|
*/
|
||||||
|
Size getSize();
|
||||||
|
|
||||||
|
void setSize(Size size);
|
||||||
|
|
||||||
|
default int getWidth() {
|
||||||
|
return getSize().getColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
default int getHeight() {
|
||||||
|
return getSize().getRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the size of the window buffer.
|
||||||
|
* Some terminals can be configured to have a buffer size
|
||||||
|
* larger than the visible window size and provide scroll bars.
|
||||||
|
* In such cases, this method should attempt to return the size
|
||||||
|
* of the whole buffer. The <code>getBufferSize()</code> method
|
||||||
|
* can be used to avoid wrapping when using the terminal in a line
|
||||||
|
* editing mode, while the {@link #getSize()} method should be
|
||||||
|
* used when using full screen mode.
|
||||||
|
*
|
||||||
|
* @return the terminal buffer size
|
||||||
|
* @see #getSize()
|
||||||
|
*/
|
||||||
|
default Size getBufferSize() {
|
||||||
|
return getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
String getType();
|
||||||
|
|
||||||
|
boolean puts(Capability capability, Object... params);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Infocmp capabilities
|
||||||
|
//
|
||||||
|
|
||||||
|
boolean getBooleanCapability(Capability capability);
|
||||||
|
|
||||||
|
Integer getNumericCapability(Capability capability);
|
||||||
|
|
||||||
|
String getStringCapability(Capability capability);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the terminal to report the cursor position.
|
||||||
|
* <p>
|
||||||
|
* As the response is read from the input stream, some
|
||||||
|
* characters may be read before the cursor position is actually
|
||||||
|
* read. Those characters can be given back using
|
||||||
|
* <code>org.jline.keymap.BindingReader#runMacro(String)</code>
|
||||||
|
*
|
||||||
|
* @param discarded a consumer receiving discarded characters
|
||||||
|
* @return <code>null</code> if cursor position reporting
|
||||||
|
* is not supported or a valid cursor position
|
||||||
|
*/
|
||||||
|
Cursor getCursorPosition(IntConsumer discarded);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <code>true</code> if the terminal has support for mouse.
|
||||||
|
*
|
||||||
|
* @return whether mouse is supported by the terminal
|
||||||
|
* @see #trackMouse(MouseTracking)
|
||||||
|
*/
|
||||||
|
boolean hasMouseSupport();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Cursor support
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the mouse tracking mouse.
|
||||||
|
* To start mouse tracking, this method must be called with a valid mouse tracking mode.
|
||||||
|
* Mouse events will be reported by writing the {@link Capability#key_mouse} to the input stream.
|
||||||
|
* When this character sequence is detected, the {@link #readMouseEvent()} method can be
|
||||||
|
* called to actually read the corresponding mouse event.
|
||||||
|
*
|
||||||
|
* @param tracking the mouse tracking mode
|
||||||
|
* @return <code>true</code> if mouse tracking is supported
|
||||||
|
*/
|
||||||
|
boolean trackMouse(MouseTracking tracking);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Mouse support
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a MouseEvent from the terminal input stream.
|
||||||
|
* Such an event must have been detected by scanning the terminal's {@link Capability#key_mouse}
|
||||||
|
* in the stream immediately before reading the event.
|
||||||
|
*
|
||||||
|
* @return the decoded mouse event.
|
||||||
|
* @see #trackMouse(MouseTracking)
|
||||||
|
*/
|
||||||
|
MouseEvent readMouseEvent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a MouseEvent from the given input stream.
|
||||||
|
*
|
||||||
|
* @param reader the input supplier
|
||||||
|
* @return the decoded mouse event
|
||||||
|
*/
|
||||||
|
MouseEvent readMouseEvent(IntSupplier reader);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <code>true</code> if the terminal has support for focus tracking.
|
||||||
|
*
|
||||||
|
* @return whether focus tracking is supported by the terminal
|
||||||
|
* @see #trackFocus(boolean)
|
||||||
|
*/
|
||||||
|
boolean hasFocusSupport();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable focus tracking mode.
|
||||||
|
* When focus tracking has been activated, each time the terminal grabs the focus,
|
||||||
|
* the string "\33[I" will be sent to the input stream and each time the focus is lost,
|
||||||
|
* the string "\33[O" will be sent to the input stream.
|
||||||
|
*
|
||||||
|
* @param tracking whether the focus tracking mode should be enabled or not
|
||||||
|
* @return <code>true</code> if focus tracking is supported
|
||||||
|
*/
|
||||||
|
boolean trackFocus(boolean tracking);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color support
|
||||||
|
*/
|
||||||
|
ColorPalette getPalette();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of signals.
|
||||||
|
*/
|
||||||
|
enum Signal {
|
||||||
|
INT,
|
||||||
|
QUIT,
|
||||||
|
TSTP,
|
||||||
|
CONT,
|
||||||
|
INFO,
|
||||||
|
WINCH
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MouseTracking {
|
||||||
|
/**
|
||||||
|
* Disable mouse tracking
|
||||||
|
*/
|
||||||
|
Off,
|
||||||
|
/**
|
||||||
|
* Track button press and release.
|
||||||
|
*/
|
||||||
|
Normal,
|
||||||
|
/**
|
||||||
|
* Also report button-motion events. Mouse movements are reported if the mouse pointer
|
||||||
|
* has moved to a different character cell.
|
||||||
|
*/
|
||||||
|
Button,
|
||||||
|
/**
|
||||||
|
* Report all motions events, even if no mouse button is down.
|
||||||
|
*/
|
||||||
|
Any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SignalHandler defines the interface used to trap signals and perform specific behaviors.
|
||||||
|
*
|
||||||
|
* @see Signal
|
||||||
|
* @see Terminal#handle(Signal, SignalHandler)
|
||||||
|
*/
|
||||||
|
interface SignalHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code SIG_DFL} value can be used to specify that the JVM default behavior
|
||||||
|
* should be used to handle this signal.
|
||||||
|
*/
|
||||||
|
SignalHandler SIG_DFL = NativeSignalHandler.SIG_DFL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code SIG_IGN} value can be used to ignore this signal and not perform
|
||||||
|
* any special processing.
|
||||||
|
*/
|
||||||
|
SignalHandler SIG_IGN = NativeSignalHandler.SIG_IGN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the signal.
|
||||||
|
*
|
||||||
|
* @param signal the signal
|
||||||
|
*/
|
||||||
|
void handle(Signal signal);
|
||||||
|
}
|
||||||
|
}
|
865
net-cli/src/main/java/org/jline/terminal/TerminalBuilder.java
Normal file
865
net-cli/src/main/java/org/jline/terminal/TerminalBuilder.java
Normal file
|
@ -0,0 +1,865 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.charset.UnsupportedCharsetException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jline.terminal.impl.AbstractPosixTerminal;
|
||||||
|
import org.jline.terminal.impl.AbstractTerminal;
|
||||||
|
import org.jline.terminal.impl.DumbTerminal;
|
||||||
|
import org.jline.terminal.impl.DumbTerminalProvider;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalExt;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
import org.jline.utils.OSUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class to create terminals.
|
||||||
|
*/
|
||||||
|
public final class TerminalBuilder {
|
||||||
|
|
||||||
|
//
|
||||||
|
// System properties
|
||||||
|
//
|
||||||
|
|
||||||
|
public static final String PROP_ENCODING = "org.jline.terminal.encoding";
|
||||||
|
public static final String PROP_CODEPAGE = "org.jline.terminal.codepage";
|
||||||
|
public static final String PROP_TYPE = "org.jline.terminal.type";
|
||||||
|
public static final String PROP_PROVIDER = "org.jline.terminal.provider";
|
||||||
|
public static final String PROP_PROVIDERS = "org.jline.terminal.providers";
|
||||||
|
public static final String PROP_PROVIDER_FFM = "ffm";
|
||||||
|
public static final String PROP_PROVIDER_JNI = "jni";
|
||||||
|
public static final String PROP_PROVIDER_JANSI = "jansi";
|
||||||
|
public static final String PROP_PROVIDER_JNA = "jna";
|
||||||
|
public static final String PROP_PROVIDER_EXEC = "exec";
|
||||||
|
public static final String PROP_PROVIDER_DUMB = "dumb";
|
||||||
|
public static final String PROP_PROVIDERS_DEFAULT = String.join(
|
||||||
|
",", PROP_PROVIDER_FFM, PROP_PROVIDER_JNI, PROP_PROVIDER_JANSI, PROP_PROVIDER_JNA, PROP_PROVIDER_EXEC);
|
||||||
|
public static final String PROP_FFM = "org.jline.terminal." + PROP_PROVIDER_FFM;
|
||||||
|
public static final String PROP_JNI = "org.jline.terminal." + PROP_PROVIDER_JNI;
|
||||||
|
public static final String PROP_JANSI = "org.jline.terminal." + PROP_PROVIDER_JANSI;
|
||||||
|
public static final String PROP_JNA = "org.jline.terminal." + PROP_PROVIDER_JNA;
|
||||||
|
public static final String PROP_EXEC = "org.jline.terminal." + PROP_PROVIDER_EXEC;
|
||||||
|
public static final String PROP_DUMB = "org.jline.terminal." + PROP_PROVIDER_DUMB;
|
||||||
|
public static final String PROP_DUMB_COLOR = "org.jline.terminal.dumb.color";
|
||||||
|
public static final String PROP_OUTPUT = "org.jline.terminal.output";
|
||||||
|
public static final String PROP_OUTPUT_OUT = "out";
|
||||||
|
public static final String PROP_OUTPUT_ERR = "err";
|
||||||
|
public static final String PROP_OUTPUT_OUT_ERR = "out-err";
|
||||||
|
public static final String PROP_OUTPUT_ERR_OUT = "err-out";
|
||||||
|
public static final String PROP_OUTPUT_FORCED_OUT = "forced-out";
|
||||||
|
public static final String PROP_OUTPUT_FORCED_ERR = "forced-err";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Other system properties controlling various jline parts
|
||||||
|
//
|
||||||
|
|
||||||
|
public static final String PROP_NON_BLOCKING_READS = "org.jline.terminal.pty.nonBlockingReads";
|
||||||
|
public static final String PROP_COLOR_DISTANCE = "org.jline.utils.colorDistance";
|
||||||
|
public static final String PROP_DISABLE_ALTERNATE_CHARSET = "org.jline.utils.disableAlternateCharset";
|
||||||
|
|
||||||
|
//
|
||||||
|
// System properties controlling how FileDescriptor are create.
|
||||||
|
// The value can be a comma separated list of defined mechanisms.
|
||||||
|
//
|
||||||
|
public static final String PROP_FILE_DESCRIPTOR_CREATION_MODE = "org.jline.terminal.pty.fileDescriptorCreationMode";
|
||||||
|
public static final String PROP_FILE_DESCRIPTOR_CREATION_MODE_NATIVE = "native";
|
||||||
|
public static final String PROP_FILE_DESCRIPTOR_CREATION_MODE_REFLECTION = "reflection";
|
||||||
|
public static final String PROP_FILE_DESCRIPTOR_CREATION_MODE_DEFAULT =
|
||||||
|
String.join(",", PROP_FILE_DESCRIPTOR_CREATION_MODE_REFLECTION, PROP_FILE_DESCRIPTOR_CREATION_MODE_NATIVE);
|
||||||
|
|
||||||
|
//
|
||||||
|
// System properties controlling how RedirectPipe are created.
|
||||||
|
// The value can be a comma separated list of defined mechanisms.
|
||||||
|
//
|
||||||
|
public static final String PROP_REDIRECT_PIPE_CREATION_MODE = "org.jline.terminal.exec.redirectPipeCreationMode";
|
||||||
|
public static final String PROP_REDIRECT_PIPE_CREATION_MODE_NATIVE = "native";
|
||||||
|
public static final String PROP_REDIRECT_PIPE_CREATION_MODE_REFLECTION = "reflection";
|
||||||
|
public static final String PROP_REDIRECT_PIPE_CREATION_MODE_DEFAULT =
|
||||||
|
String.join(",", PROP_REDIRECT_PIPE_CREATION_MODE_REFLECTION, PROP_REDIRECT_PIPE_CREATION_MODE_NATIVE);
|
||||||
|
|
||||||
|
public static final Set<String> DEPRECATED_PROVIDERS =
|
||||||
|
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(PROP_PROVIDER_JNA, PROP_PROVIDER_JANSI)));
|
||||||
|
|
||||||
|
public static final String PROP_DISABLE_DEPRECATED_PROVIDER_WARNING =
|
||||||
|
"org.jline.terminal.disableDeprecatedProviderWarning";
|
||||||
|
private static final AtomicReference<Terminal> SYSTEM_TERMINAL = new AtomicReference<>();
|
||||||
|
private static final AtomicReference<Terminal> TERMINAL_OVERRIDE = new AtomicReference<>();
|
||||||
|
private static final int UTF8_CODE_PAGE = 65001;
|
||||||
|
private String name;
|
||||||
|
private InputStream in;
|
||||||
|
private OutputStream out;
|
||||||
|
private String type;
|
||||||
|
private Charset encoding;
|
||||||
|
private int codepage;
|
||||||
|
private Boolean system;
|
||||||
|
private SystemOutput systemOutput;
|
||||||
|
private String provider;
|
||||||
|
private String providers;
|
||||||
|
private Boolean jna;
|
||||||
|
private Boolean jansi;
|
||||||
|
private Boolean jni;
|
||||||
|
private Boolean exec;
|
||||||
|
private Boolean ffm;
|
||||||
|
private Boolean dumb;
|
||||||
|
private Boolean color;
|
||||||
|
private Attributes attributes;
|
||||||
|
private Size size;
|
||||||
|
private boolean nativeSignals = true;
|
||||||
|
private Terminal.SignalHandler signalHandler = Terminal.SignalHandler.SIG_DFL;
|
||||||
|
private boolean paused = false;
|
||||||
|
private TerminalBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default system terminal.
|
||||||
|
* Terminals should be closed properly using the {@link Terminal#close()}
|
||||||
|
* method in order to restore the original terminal state.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This call is equivalent to:
|
||||||
|
* <code>builder().build()</code>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return the default system terminal
|
||||||
|
* @throws IOException if an error occurs
|
||||||
|
*/
|
||||||
|
public static Terminal terminal() throws IOException {
|
||||||
|
return builder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new terminal builder instance.
|
||||||
|
*
|
||||||
|
* @return a builder
|
||||||
|
*/
|
||||||
|
public static TerminalBuilder builder() {
|
||||||
|
return new TerminalBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SystemStream select(Map<SystemStream, Boolean> system, SystemStream... streams) {
|
||||||
|
for (SystemStream s : streams) {
|
||||||
|
if (system.get(s)) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getParentProcessCommand() {
|
||||||
|
try {
|
||||||
|
Class<?> phClass = Class.forName("java.lang.ProcessHandle");
|
||||||
|
Object current = phClass.getMethod("current").invoke(null);
|
||||||
|
Object parent = ((Optional<?>) phClass.getMethod("parent").invoke(current)).orElse(null);
|
||||||
|
Method infoMethod = phClass.getMethod("info");
|
||||||
|
Object info = infoMethod.invoke(parent);
|
||||||
|
Object command = ((Optional<?>)
|
||||||
|
infoMethod.getReturnType().getMethod("command").invoke(info))
|
||||||
|
.orElse(null);
|
||||||
|
return (String) command;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Boolean getBoolean(String name, Boolean def) {
|
||||||
|
try {
|
||||||
|
String str = System.getProperty(name);
|
||||||
|
if (str != null) {
|
||||||
|
return Boolean.parseBoolean(str);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <S> S load(Class<S> clazz) {
|
||||||
|
return ServiceLoader.load(clazz, clazz.getClassLoader()).iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Charset getCodepageCharset(int codepage) {
|
||||||
|
// http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
|
||||||
|
if (codepage == UTF8_CODE_PAGE) {
|
||||||
|
return StandardCharsets.UTF_8;
|
||||||
|
}
|
||||||
|
String charsetMS = "ms" + codepage;
|
||||||
|
if (Charset.isSupported(charsetMS)) {
|
||||||
|
return Charset.forName(charsetMS);
|
||||||
|
}
|
||||||
|
String charsetCP = "cp" + codepage;
|
||||||
|
if (Charset.isSupported(charsetCP)) {
|
||||||
|
return Charset.forName(charsetCP);
|
||||||
|
}
|
||||||
|
return Charset.defaultCharset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows an application to override the result of {@link #build()}. The
|
||||||
|
* intended use case is to allow a container or server application to control
|
||||||
|
* an embedded application that uses a LineReader that uses Terminal
|
||||||
|
* constructed with TerminalBuilder.build but provides no public api for setting
|
||||||
|
* the <code>LineReader</code> of the {@link Terminal}. For example, the sbt
|
||||||
|
* build tool uses a <code>LineReader</code> to implement an interactive shell.
|
||||||
|
* One of its supported commands is <code>console</code> which invokes
|
||||||
|
* the scala REPL. The scala REPL also uses a <code>LineReader</code> and it
|
||||||
|
* is necessary to override the {@link Terminal} used by the the REPL to
|
||||||
|
* share the same {@link Terminal} instance used by sbt.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* When this method is called with a non-null {@link Terminal}, all subsequent
|
||||||
|
* calls to {@link #build()} will return the provided {@link Terminal} regardless
|
||||||
|
* of how the {@link TerminalBuilder} was constructed. The default behavior
|
||||||
|
* of {@link TerminalBuilder} can be restored by calling setTerminalOverride
|
||||||
|
* with a null {@link Terminal}
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Usage of setTerminalOverride should be restricted to cases where it
|
||||||
|
* isn't possible to update the api of the nested application to accept
|
||||||
|
* a {@link Terminal instance}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param terminal the {@link Terminal} to globally override
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static void setTerminalOverride(final Terminal terminal) {
|
||||||
|
TERMINAL_OVERRIDE.set(terminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalBuilder name(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalBuilder streams(InputStream in, OutputStream out) {
|
||||||
|
this.in = in;
|
||||||
|
this.out = out;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalBuilder system(boolean system) {
|
||||||
|
this.system = system;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates which standard stream should be used when displaying to the terminal.
|
||||||
|
* The default is to use the system output stream.
|
||||||
|
* Building a system terminal will fail if one of the stream specified is not linked
|
||||||
|
* to the controlling terminal.
|
||||||
|
*
|
||||||
|
* @param systemOutput The mode to choose the output stream.
|
||||||
|
* @return The builder.
|
||||||
|
*/
|
||||||
|
public TerminalBuilder systemOutput(SystemOutput systemOutput) {
|
||||||
|
this.systemOutput = systemOutput;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the usage of the give terminal provider.
|
||||||
|
*
|
||||||
|
* @param provider The {@link TerminalProvider}'s name to use when creating the Terminal.
|
||||||
|
* @return The builder.
|
||||||
|
*/
|
||||||
|
public TerminalBuilder provider(String provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of providers to try when creating the terminal.
|
||||||
|
* If not specified, the system property {@link #PROP_PROVIDERS} will be used if set.
|
||||||
|
* Else, the value {@link #PROP_PROVIDERS_DEFAULT} will be used.
|
||||||
|
*
|
||||||
|
* @param providers The list of {@link TerminalProvider}'s names to check when creating the Terminal.
|
||||||
|
* @return The builder.
|
||||||
|
*/
|
||||||
|
public TerminalBuilder providers(String providers) {
|
||||||
|
this.providers = providers;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the {@link #PROP_PROVIDER_JNA}/{@code jna} terminal provider.
|
||||||
|
* If not specified, the system property {@link #PROP_JNA} will be used if set.
|
||||||
|
* If not specified, the provider will be checked.
|
||||||
|
*/
|
||||||
|
public TerminalBuilder jna(boolean jna) {
|
||||||
|
this.jna = jna;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the {@link #PROP_PROVIDER_JANSI}/{@code jansi} terminal provider.
|
||||||
|
* If not specified, the system property {@link #PROP_JANSI} will be used if set.
|
||||||
|
* If not specified, the provider will be checked.
|
||||||
|
*/
|
||||||
|
public TerminalBuilder jansi(boolean jansi) {
|
||||||
|
this.jansi = jansi;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the {@link #PROP_PROVIDER_JNI}/{@code jni} terminal provider.
|
||||||
|
* If not specified, the system property {@link #PROP_JNI} will be used if set.
|
||||||
|
* If not specified, the provider will be checked.
|
||||||
|
*/
|
||||||
|
public TerminalBuilder jni(boolean jni) {
|
||||||
|
this.jni = jni;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the {@link #PROP_PROVIDER_EXEC}/{@code exec} terminal provider.
|
||||||
|
* If not specified, the system property {@link #PROP_EXEC} will be used if set.
|
||||||
|
* If not specified, the provider will be checked.
|
||||||
|
*/
|
||||||
|
public TerminalBuilder exec(boolean exec) {
|
||||||
|
this.exec = exec;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the {@link #PROP_PROVIDER_FFM}/{@code ffm} terminal provider.
|
||||||
|
* If not specified, the system property {@link #PROP_FFM} will be used if set.
|
||||||
|
* If not specified, the provider will be checked.
|
||||||
|
*/
|
||||||
|
public TerminalBuilder ffm(boolean ffm) {
|
||||||
|
this.ffm = ffm;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the {@link #PROP_PROVIDER_DUMB}/{@code dumb} terminal provider.
|
||||||
|
* If not specified, the system property {@link #PROP_DUMB} will be used if set.
|
||||||
|
* If not specified, the provider will be checked.
|
||||||
|
*/
|
||||||
|
public TerminalBuilder dumb(boolean dumb) {
|
||||||
|
this.dumb = dumb;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalBuilder type(String type) {
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalBuilder color(boolean color) {
|
||||||
|
this.color = color;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the encoding to use for reading/writing from the console.
|
||||||
|
* If {@code null} (the default value), JLine will automatically select
|
||||||
|
* a {@link Charset}, usually the default system encoding. However,
|
||||||
|
* on some platforms (e.g. Windows) it may use a different one depending
|
||||||
|
* on the {@link Terminal} implementation.
|
||||||
|
*
|
||||||
|
* <p>Use {@link Terminal#encoding()} to get the {@link Charset} that
|
||||||
|
* should be used for a {@link Terminal}.</p>
|
||||||
|
*
|
||||||
|
* @param encoding The encoding to use or null to automatically select one
|
||||||
|
* @return The builder
|
||||||
|
* @throws UnsupportedCharsetException If the given encoding is not supported
|
||||||
|
* @see Terminal#encoding()
|
||||||
|
*/
|
||||||
|
public TerminalBuilder encoding(String encoding) throws UnsupportedCharsetException {
|
||||||
|
return encoding(encoding != null ? Charset.forName(encoding) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link Charset} to use for reading/writing from the console.
|
||||||
|
* If {@code null} (the default value), JLine will automatically select
|
||||||
|
* a {@link Charset}, usually the default system encoding. However,
|
||||||
|
* on some platforms (e.g. Windows) it may use a different one depending
|
||||||
|
* on the {@link Terminal} implementation.
|
||||||
|
*
|
||||||
|
* <p>Use {@link Terminal#encoding()} to get the {@link Charset} that
|
||||||
|
* should be used to read/write from a {@link Terminal}.</p>
|
||||||
|
*
|
||||||
|
* @param encoding The encoding to use or null to automatically select one
|
||||||
|
* @return The builder
|
||||||
|
* @see Terminal#encoding()
|
||||||
|
*/
|
||||||
|
public TerminalBuilder encoding(Charset encoding) {
|
||||||
|
this.encoding = encoding;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param codepage the codepage
|
||||||
|
* @return The builder
|
||||||
|
* @deprecated JLine now writes Unicode output independently from the selected
|
||||||
|
* code page. Using this option will only make it emulate the selected code
|
||||||
|
* page for {@link Terminal#input()} and {@link Terminal#output()}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public TerminalBuilder codepage(int codepage) {
|
||||||
|
this.codepage = codepage;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attributes to use when creating a non system terminal,
|
||||||
|
* i.e. when the builder has been given the input and
|
||||||
|
* output streams using the {@link #streams(InputStream, OutputStream)} method
|
||||||
|
* or when {@link #system(boolean)} has been explicitly called with
|
||||||
|
* <code>false</code>.
|
||||||
|
*
|
||||||
|
* @param attributes the attributes to use
|
||||||
|
* @return The builder
|
||||||
|
* @see #size(Size)
|
||||||
|
* @see #system(boolean)
|
||||||
|
*/
|
||||||
|
public TerminalBuilder attributes(Attributes attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial size to use when creating a non system terminal,
|
||||||
|
* i.e. when the builder has been given the input and
|
||||||
|
* output streams using the {@link #streams(InputStream, OutputStream)} method
|
||||||
|
* or when {@link #system(boolean)} has been explicitly called with
|
||||||
|
* <code>false</code>.
|
||||||
|
*
|
||||||
|
* @param size the initial size
|
||||||
|
* @return The builder
|
||||||
|
* @see #attributes(Attributes)
|
||||||
|
* @see #system(boolean)
|
||||||
|
*/
|
||||||
|
public TerminalBuilder size(Size size) {
|
||||||
|
this.size = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalBuilder nativeSignals(boolean nativeSignals) {
|
||||||
|
this.nativeSignals = nativeSignals;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the default value for signal handlers.
|
||||||
|
* All signals will be mapped to the given handler.
|
||||||
|
*
|
||||||
|
* @param signalHandler the default signal handler
|
||||||
|
* @return The builder
|
||||||
|
*/
|
||||||
|
public TerminalBuilder signalHandler(Terminal.SignalHandler signalHandler) {
|
||||||
|
this.signalHandler = signalHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial paused state of the terminal (defaults to false).
|
||||||
|
* By default, the terminal is started, but in some cases,
|
||||||
|
* one might want to make sure the input stream is not consumed
|
||||||
|
* before needed, in which case the terminal needs to be created
|
||||||
|
* in a paused state.
|
||||||
|
*
|
||||||
|
* @param paused the initial paused state
|
||||||
|
* @return The builder
|
||||||
|
* @see Terminal#pause()
|
||||||
|
*/
|
||||||
|
public TerminalBuilder paused(boolean paused) {
|
||||||
|
this.paused = paused;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the terminal.
|
||||||
|
*
|
||||||
|
* @return the newly created terminal, never {@code null}
|
||||||
|
* @throws IOException if an error occurs
|
||||||
|
*/
|
||||||
|
public Terminal build() throws IOException {
|
||||||
|
Terminal override = TERMINAL_OVERRIDE.get();
|
||||||
|
Terminal terminal = override != null ? override : doBuild();
|
||||||
|
if (override != null) {
|
||||||
|
Log.debug(() -> "Overriding terminal with global value set by TerminalBuilder.setTerminalOverride");
|
||||||
|
}
|
||||||
|
Log.debug(() -> "Using terminal " + terminal.getClass().getSimpleName());
|
||||||
|
if (terminal instanceof AbstractPosixTerminal) {
|
||||||
|
Log.debug(() -> "Using pty "
|
||||||
|
+ ((AbstractPosixTerminal) terminal).getPty().getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
return terminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Terminal doBuild() throws IOException {
|
||||||
|
String name = this.name;
|
||||||
|
if (name == null) {
|
||||||
|
name = "JLine terminal";
|
||||||
|
}
|
||||||
|
Charset encoding = computeEncoding();
|
||||||
|
String type = computeType();
|
||||||
|
|
||||||
|
String provider = this.provider;
|
||||||
|
if (provider == null) {
|
||||||
|
provider = System.getProperty(PROP_PROVIDER, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean forceDumb =
|
||||||
|
(DumbTerminal.TYPE_DUMB.equals(type) || type != null && type.startsWith(DumbTerminal.TYPE_DUMB_COLOR))
|
||||||
|
|| (provider != null && provider.equals(PROP_PROVIDER_DUMB));
|
||||||
|
Boolean dumb = this.dumb;
|
||||||
|
if (dumb == null) {
|
||||||
|
dumb = getBoolean(PROP_DUMB, null);
|
||||||
|
}
|
||||||
|
IllegalStateException exception = new IllegalStateException("Unable to create a terminal");
|
||||||
|
List<TerminalProvider> providers = getProviders(provider, exception);
|
||||||
|
Terminal terminal = null;
|
||||||
|
if ((system != null && system) || (system == null && in == null && out == null)) {
|
||||||
|
if (system != null
|
||||||
|
&& ((in != null && !in.equals(System.in))
|
||||||
|
|| (out != null && !out.equals(System.out) && !out.equals(System.err)))) {
|
||||||
|
throw new IllegalArgumentException("Cannot create a system terminal using non System streams");
|
||||||
|
}
|
||||||
|
if (attributes != null || size != null) {
|
||||||
|
Log.warn("Attributes and size fields are ignored when creating a system terminal");
|
||||||
|
}
|
||||||
|
SystemOutput systemOutput = computeSystemOutput();
|
||||||
|
Map<SystemStream, Boolean> system = Stream.of(SystemStream.values())
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
stream -> stream, stream -> providers.stream().anyMatch(p -> p.isSystemStream(stream))));
|
||||||
|
SystemStream systemStream = select(system, systemOutput);
|
||||||
|
|
||||||
|
if (!forceDumb && system.get(SystemStream.Input) && systemStream != null) {
|
||||||
|
if (attributes != null || size != null) {
|
||||||
|
Log.warn("Attributes and size fields are ignored when creating a system terminal");
|
||||||
|
}
|
||||||
|
boolean ansiPassThrough = OSUtils.IS_CONEMU;
|
||||||
|
// Cygwin defaults to XTERM, but actually supports 256 colors,
|
||||||
|
// so if the value comes from the environment, change it to xterm-256color
|
||||||
|
if ((OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM)
|
||||||
|
&& "xterm".equals(type)
|
||||||
|
&& this.type == null
|
||||||
|
&& System.getProperty(PROP_TYPE) == null) {
|
||||||
|
type = "xterm-256color";
|
||||||
|
}
|
||||||
|
for (TerminalProvider prov : providers) {
|
||||||
|
if (terminal == null) {
|
||||||
|
try {
|
||||||
|
terminal = prov.sysTerminal(
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
ansiPassThrough,
|
||||||
|
encoding,
|
||||||
|
nativeSignals,
|
||||||
|
signalHandler,
|
||||||
|
paused,
|
||||||
|
systemStream);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.debug("Error creating " + prov.name() + " based terminal: ", t.getMessage(), t);
|
||||||
|
exception.addSuppressed(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (terminal == null && OSUtils.IS_WINDOWS && providers.isEmpty() && (dumb == null || !dumb)) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Unable to create a system terminal. On Windows, either JLine's native libraries, JNA "
|
||||||
|
+ "or Jansi library is required. Make sure to add one of those in the classpath.",
|
||||||
|
exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (terminal instanceof AbstractTerminal t) {
|
||||||
|
if (SYSTEM_TERMINAL.compareAndSet(null, t)) {
|
||||||
|
t.setOnClose(() -> SYSTEM_TERMINAL.compareAndSet(t, null));
|
||||||
|
} else {
|
||||||
|
exception.addSuppressed(new IllegalStateException("A system terminal is already running. "
|
||||||
|
+ "Make sure to use the created system Terminal on the LineReaderBuilder if you're using one "
|
||||||
|
+ "or that previously created system Terminals have been correctly closed."));
|
||||||
|
terminal.close();
|
||||||
|
terminal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (terminal == null && (forceDumb || dumb == null || dumb)) {
|
||||||
|
if (!forceDumb && dumb == null) {
|
||||||
|
if (Log.isDebugEnabled()) {
|
||||||
|
Log.warn("input is tty: " + system.get(SystemStream.Input));
|
||||||
|
Log.warn("output is tty: " + system.get(SystemStream.Output));
|
||||||
|
Log.warn("error is tty: " + system.get(SystemStream.Error));
|
||||||
|
Log.warn("Creating a dumb terminal", exception);
|
||||||
|
} else {
|
||||||
|
Log.warn(
|
||||||
|
"Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type = getDumbTerminalType(dumb, systemStream);
|
||||||
|
terminal = new DumbTerminalProvider()
|
||||||
|
.sysTerminal(name, type, false, encoding, nativeSignals, signalHandler, paused, systemStream);
|
||||||
|
if (OSUtils.IS_WINDOWS) {
|
||||||
|
Attributes attr = terminal.getAttributes();
|
||||||
|
attr.setInputFlag(Attributes.InputFlag.IGNCR, true);
|
||||||
|
terminal.setAttributes(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (TerminalProvider prov : providers) {
|
||||||
|
if (terminal == null) {
|
||||||
|
try {
|
||||||
|
terminal = prov.newTerminal(
|
||||||
|
name, type, in, out, encoding, signalHandler, paused, attributes, size);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.debug("Error creating " + prov.name() + " based terminal: ", t.getMessage(), t);
|
||||||
|
exception.addSuppressed(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (terminal == null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
if (terminal instanceof TerminalExt te) {
|
||||||
|
if (DEPRECATED_PROVIDERS.contains(te.getProvider().name())
|
||||||
|
&& !getBoolean(PROP_DISABLE_DEPRECATED_PROVIDER_WARNING, false)) {
|
||||||
|
Log.warn("The terminal provider " + te.getProvider().name()
|
||||||
|
+ " has been deprecated, check your configuration. This warning can be disabled by setting the system property "
|
||||||
|
+ PROP_DISABLE_DEPRECATED_PROVIDER_WARNING + " to true.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return terminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDumbTerminalType(Boolean dumb, SystemStream systemStream) {
|
||||||
|
// forced colored dumb terminal
|
||||||
|
Boolean color = this.color;
|
||||||
|
if (color == null) {
|
||||||
|
color = getBoolean(PROP_DUMB_COLOR, null);
|
||||||
|
}
|
||||||
|
if (dumb == null) {
|
||||||
|
// detect emacs using the env variable
|
||||||
|
if (color == null) {
|
||||||
|
String emacs = System.getenv("INSIDE_EMACS");
|
||||||
|
if (emacs != null && emacs.contains("comint")) {
|
||||||
|
color = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// detect Intellij Idea
|
||||||
|
if (color == null) {
|
||||||
|
// using the env variable on windows
|
||||||
|
String ideHome = System.getenv("IDE_HOME");
|
||||||
|
if (ideHome != null) {
|
||||||
|
color = true;
|
||||||
|
} else {
|
||||||
|
// using the parent process command on unix/mac
|
||||||
|
String command = getParentProcessCommand();
|
||||||
|
if (command != null && command.endsWith("/idea")) {
|
||||||
|
color = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (color == null) {
|
||||||
|
color = systemStream != null && System.getenv("TERM") != null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (color == null) {
|
||||||
|
color = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemOutput computeSystemOutput() {
|
||||||
|
SystemOutput systemOutput = null;
|
||||||
|
if (out != null) {
|
||||||
|
if (out.equals(System.out)) {
|
||||||
|
systemOutput = SystemOutput.SysOut;
|
||||||
|
} else if (out.equals(System.err)) {
|
||||||
|
systemOutput = SystemOutput.SysErr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (systemOutput == null) {
|
||||||
|
systemOutput = this.systemOutput;
|
||||||
|
}
|
||||||
|
if (systemOutput == null) {
|
||||||
|
String str = System.getProperty(PROP_OUTPUT);
|
||||||
|
if (str != null) {
|
||||||
|
switch (str.trim().toLowerCase(Locale.ROOT)) {
|
||||||
|
case PROP_OUTPUT_OUT:
|
||||||
|
systemOutput = SystemOutput.SysOut;
|
||||||
|
break;
|
||||||
|
case PROP_OUTPUT_ERR:
|
||||||
|
systemOutput = SystemOutput.SysErr;
|
||||||
|
break;
|
||||||
|
case PROP_OUTPUT_OUT_ERR:
|
||||||
|
systemOutput = SystemOutput.SysOutOrSysErr;
|
||||||
|
break;
|
||||||
|
case PROP_OUTPUT_ERR_OUT:
|
||||||
|
systemOutput = SystemOutput.SysErrOrSysOut;
|
||||||
|
break;
|
||||||
|
case PROP_OUTPUT_FORCED_OUT:
|
||||||
|
systemOutput = SystemOutput.ForcedSysOut;
|
||||||
|
break;
|
||||||
|
case PROP_OUTPUT_FORCED_ERR:
|
||||||
|
systemOutput = SystemOutput.ForcedSysErr;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.debug("Unsupported value for " + PROP_OUTPUT + ": " + str + ". Supported values are: "
|
||||||
|
+ String.join(
|
||||||
|
", ",
|
||||||
|
PROP_OUTPUT_OUT,
|
||||||
|
PROP_OUTPUT_ERR,
|
||||||
|
PROP_OUTPUT_OUT_ERR,
|
||||||
|
PROP_OUTPUT_ERR_OUT)
|
||||||
|
+ ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (systemOutput == null) {
|
||||||
|
systemOutput = SystemOutput.SysOutOrSysErr;
|
||||||
|
}
|
||||||
|
return systemOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String computeType() {
|
||||||
|
String type = this.type;
|
||||||
|
if (type == null) {
|
||||||
|
type = System.getProperty(PROP_TYPE);
|
||||||
|
}
|
||||||
|
if (type == null) {
|
||||||
|
type = System.getenv("TERM");
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Charset computeEncoding() {
|
||||||
|
Charset encoding = this.encoding;
|
||||||
|
if (encoding == null) {
|
||||||
|
String charsetName = System.getProperty(PROP_ENCODING);
|
||||||
|
if (charsetName != null && Charset.isSupported(charsetName)) {
|
||||||
|
encoding = Charset.forName(charsetName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (encoding == null) {
|
||||||
|
int codepage = this.codepage;
|
||||||
|
if (codepage <= 0) {
|
||||||
|
String str = System.getProperty(PROP_CODEPAGE);
|
||||||
|
if (str != null) {
|
||||||
|
codepage = Integer.parseInt(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (codepage >= 0) {
|
||||||
|
encoding = getCodepageCharset(codepage);
|
||||||
|
} else {
|
||||||
|
encoding = StandardCharsets.UTF_8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of available terminal providers.
|
||||||
|
* This list is sorted according to the {@link #PROP_PROVIDERS} system property.
|
||||||
|
*
|
||||||
|
* @param provider if not {@code null}, only this provider will be checked
|
||||||
|
* @param exception if a provider throws an exception, it will be added to this exception as a suppressed exception
|
||||||
|
* @return a list of terminal providers
|
||||||
|
*/
|
||||||
|
public List<TerminalProvider> getProviders(String provider, IllegalStateException exception) {
|
||||||
|
List<TerminalProvider> providers = new ArrayList<>();
|
||||||
|
// Check ffm provider
|
||||||
|
checkProvider(provider, exception, providers, ffm, PROP_FFM, PROP_PROVIDER_FFM);
|
||||||
|
// Check jni provider
|
||||||
|
checkProvider(provider, exception, providers, jni, PROP_JNI, PROP_PROVIDER_JNI);
|
||||||
|
// Check jansi provider
|
||||||
|
checkProvider(provider, exception, providers, jansi, PROP_JANSI, PROP_PROVIDER_JANSI);
|
||||||
|
// Check jna provider
|
||||||
|
checkProvider(provider, exception, providers, jna, PROP_JNA, PROP_PROVIDER_JNA);
|
||||||
|
// Check exec provider
|
||||||
|
checkProvider(provider, exception, providers, exec, PROP_EXEC, PROP_PROVIDER_EXEC);
|
||||||
|
// Order providers
|
||||||
|
List<String> order = Arrays.asList(
|
||||||
|
(this.providers != null ? this.providers : System.getProperty(PROP_PROVIDERS, PROP_PROVIDERS_DEFAULT))
|
||||||
|
.split(","));
|
||||||
|
providers.sort(Comparator.comparing(l -> {
|
||||||
|
int idx = order.indexOf(l.name());
|
||||||
|
return idx >= 0 ? idx : Integer.MAX_VALUE;
|
||||||
|
}));
|
||||||
|
String names = providers.stream().map(TerminalProvider::name).collect(Collectors.joining(", "));
|
||||||
|
Log.debug("Available providers: " + names);
|
||||||
|
return providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkProvider(
|
||||||
|
String provider,
|
||||||
|
IllegalStateException exception,
|
||||||
|
List<TerminalProvider> providers,
|
||||||
|
Boolean load,
|
||||||
|
String property,
|
||||||
|
String name) {
|
||||||
|
Boolean doLoad = provider != null ? (Boolean) name.equals(provider) : load;
|
||||||
|
if (doLoad == null) {
|
||||||
|
doLoad = getBoolean(property, true);
|
||||||
|
}
|
||||||
|
if (doLoad) {
|
||||||
|
try {
|
||||||
|
TerminalProvider prov = TerminalProvider.load(name);
|
||||||
|
prov.isSystemStream(SystemStream.Output);
|
||||||
|
providers.add(prov);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.debug("Unable to load " + name + " provider: ", t);
|
||||||
|
exception.addSuppressed(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SystemStream select(Map<SystemStream, Boolean> system, SystemOutput systemOutput) {
|
||||||
|
switch (systemOutput) {
|
||||||
|
case SysOut:
|
||||||
|
return select(system, SystemStream.Output);
|
||||||
|
case SysErr:
|
||||||
|
return select(system, SystemStream.Error);
|
||||||
|
case SysOutOrSysErr:
|
||||||
|
return select(system, SystemStream.Output, SystemStream.Error);
|
||||||
|
case SysErrOrSysOut:
|
||||||
|
return select(system, SystemStream.Error, SystemStream.Output);
|
||||||
|
case ForcedSysOut:
|
||||||
|
return SystemStream.Output;
|
||||||
|
case ForcedSysErr:
|
||||||
|
return SystemStream.Error;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Terminal output control
|
||||||
|
//
|
||||||
|
public enum SystemOutput {
|
||||||
|
SysOut,
|
||||||
|
SysErr,
|
||||||
|
SysOutOrSysErr,
|
||||||
|
SysErrOrSysOut,
|
||||||
|
ForcedSysOut,
|
||||||
|
ForcedSysErr
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOError;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Cursor;
|
||||||
|
import org.jline.terminal.Size;
|
||||||
|
import org.jline.terminal.spi.Pty;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
|
||||||
|
public abstract class AbstractPosixTerminal extends AbstractTerminal {
|
||||||
|
|
||||||
|
protected final Pty pty;
|
||||||
|
protected final Attributes originalAttributes;
|
||||||
|
|
||||||
|
public AbstractPosixTerminal(String name, String type, Pty pty) throws IOException {
|
||||||
|
this(name, type, pty, null, SignalHandler.SIG_DFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractPosixTerminal(String name, String type, Pty pty, Charset encoding, SignalHandler signalHandler)
|
||||||
|
throws IOException {
|
||||||
|
super(name, type, encoding, signalHandler);
|
||||||
|
Objects.requireNonNull(pty);
|
||||||
|
this.pty = pty;
|
||||||
|
this.originalAttributes = this.pty.getAttr();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pty getPty() {
|
||||||
|
return pty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attributes getAttributes() {
|
||||||
|
try {
|
||||||
|
return pty.getAttr();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Attributes attr) {
|
||||||
|
try {
|
||||||
|
pty.setAttr(attr);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Size getSize() {
|
||||||
|
try {
|
||||||
|
return pty.getSize();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(Size size) {
|
||||||
|
try {
|
||||||
|
pty.setSize(size);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doClose() throws IOException {
|
||||||
|
super.doClose();
|
||||||
|
pty.setAttr(originalAttributes);
|
||||||
|
pty.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor getCursorPosition(IntConsumer discarded) {
|
||||||
|
return CursorSupport.getCursorPosition(this, discarded);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalProvider getProvider() {
|
||||||
|
return getPty().getProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SystemStream getSystemStream() {
|
||||||
|
return getPty().getSystemStream();
|
||||||
|
}
|
||||||
|
}
|
234
net-cli/src/main/java/org/jline/terminal/impl/AbstractPty.java
Normal file
234
net-cli/src/main/java/org/jline/terminal/impl/AbstractPty.java
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2019, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOError;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.spi.Pty;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
import org.jline.utils.NonBlockingInputStream;
|
||||||
|
import static org.jline.terminal.TerminalBuilder.PROP_FILE_DESCRIPTOR_CREATION_MODE;
|
||||||
|
import static org.jline.terminal.TerminalBuilder.PROP_FILE_DESCRIPTOR_CREATION_MODE_DEFAULT;
|
||||||
|
import static org.jline.terminal.TerminalBuilder.PROP_FILE_DESCRIPTOR_CREATION_MODE_REFLECTION;
|
||||||
|
import static org.jline.terminal.TerminalBuilder.PROP_NON_BLOCKING_READS;
|
||||||
|
|
||||||
|
public abstract class AbstractPty implements Pty {
|
||||||
|
|
||||||
|
private static FileDescriptorCreator fileDescriptorCreator;
|
||||||
|
protected final TerminalProvider provider;
|
||||||
|
protected final SystemStream systemStream;
|
||||||
|
private Attributes current;
|
||||||
|
private boolean skipNextLf;
|
||||||
|
|
||||||
|
public AbstractPty(TerminalProvider provider, SystemStream systemStream) {
|
||||||
|
this.provider = provider;
|
||||||
|
this.systemStream = systemStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static FileDescriptor newDescriptor(int fd) {
|
||||||
|
if (fileDescriptorCreator == null) {
|
||||||
|
String str =
|
||||||
|
System.getProperty(PROP_FILE_DESCRIPTOR_CREATION_MODE, PROP_FILE_DESCRIPTOR_CREATION_MODE_DEFAULT);
|
||||||
|
String[] modes = str.split(",");
|
||||||
|
IllegalStateException ise = new IllegalStateException("Unable to create FileDescriptor");
|
||||||
|
for (String mode : modes) {
|
||||||
|
try {
|
||||||
|
switch (mode) {
|
||||||
|
case PROP_FILE_DESCRIPTOR_CREATION_MODE_REFLECTION:
|
||||||
|
fileDescriptorCreator = new ReflectionFileDescriptorCreator();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// ignore
|
||||||
|
ise.addSuppressed(t);
|
||||||
|
}
|
||||||
|
if (fileDescriptorCreator != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fileDescriptorCreator == null) {
|
||||||
|
throw ise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileDescriptorCreator.newDescriptor(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttr(Attributes attr) throws IOException {
|
||||||
|
current = new Attributes(attr);
|
||||||
|
doSetAttr(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getSlaveInput() throws IOException {
|
||||||
|
InputStream si = doGetSlaveInput();
|
||||||
|
InputStream nsi = new FilterInputStream(si) {
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
for (; ; ) {
|
||||||
|
int c = super.read();
|
||||||
|
if (current.getInputFlag(Attributes.InputFlag.INORMEOL)) {
|
||||||
|
if (c == '\r') {
|
||||||
|
skipNextLf = true;
|
||||||
|
c = '\n';
|
||||||
|
} else if (c == '\n') {
|
||||||
|
if (skipNextLf) {
|
||||||
|
skipNextLf = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
skipNextLf = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (Boolean.parseBoolean(System.getProperty(PROP_NON_BLOCKING_READS, "true"))) {
|
||||||
|
return new PtyInputStream(nsi);
|
||||||
|
} else {
|
||||||
|
return nsi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doSetAttr(Attributes attr) throws IOException;
|
||||||
|
|
||||||
|
protected abstract InputStream doGetSlaveInput() throws IOException;
|
||||||
|
|
||||||
|
protected void checkInterrupted() throws InterruptedIOException {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new InterruptedIOException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalProvider getProvider() {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SystemStream getSystemStream() {
|
||||||
|
return systemStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileDescriptorCreator {
|
||||||
|
FileDescriptor newDescriptor(int fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflection based file descriptor creator.
|
||||||
|
* This requires the following option
|
||||||
|
* --add-opens java.base/java.io=ALL-UNNAMED
|
||||||
|
*/
|
||||||
|
static class ReflectionFileDescriptorCreator implements FileDescriptorCreator {
|
||||||
|
private final Field fileDescriptorField;
|
||||||
|
|
||||||
|
ReflectionFileDescriptorCreator() throws Exception {
|
||||||
|
Field field = FileDescriptor.class.getDeclaredField("fd");
|
||||||
|
field.setAccessible(true);
|
||||||
|
fileDescriptorField = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileDescriptor newDescriptor(int fd) {
|
||||||
|
FileDescriptor descriptor = new FileDescriptor();
|
||||||
|
try {
|
||||||
|
fileDescriptorField.set(descriptor, fd);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
// This should not happen as the field has been set accessible
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class that could be used on OpenJDK 17. However, it requires the following JVM option
|
||||||
|
* --add-exports java.base/jdk.internal.access=ALL-UNNAMED
|
||||||
|
* so the benefit does not seem important enough to warrant the problems caused
|
||||||
|
* by access the jdk.internal.access package at compile time, which itself requires
|
||||||
|
* custom compiler options and a different maven module, or at least a different compile
|
||||||
|
* phase with a JDK 17 compiler.
|
||||||
|
* So, just keep the ReflectionFileDescriptorCreator for now.
|
||||||
|
*
|
||||||
|
static class Jdk17FileDescriptorCreator implements FileDescriptorCreator {
|
||||||
|
private final jdk.internal.access.JavaIOFileDescriptorAccess fdAccess;
|
||||||
|
Jdk17FileDescriptorCreator() {
|
||||||
|
fdAccess = jdk.internal.access.SharedSecrets.getJavaIOFileDescriptorAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileDescriptor newDescriptor(int fd) {
|
||||||
|
FileDescriptor descriptor = new FileDescriptor();
|
||||||
|
fdAccess.set(descriptor, fd);
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PtyInputStream extends NonBlockingInputStream {
|
||||||
|
final InputStream in;
|
||||||
|
int c = 0;
|
||||||
|
|
||||||
|
PtyInputStream(InputStream in) {
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(long timeout, boolean isPeek) throws IOException {
|
||||||
|
checkInterrupted();
|
||||||
|
if (c != 0) {
|
||||||
|
int r = c;
|
||||||
|
if (!isPeek) {
|
||||||
|
c = 0;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
} else {
|
||||||
|
setNonBlocking();
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (true) {
|
||||||
|
int r = in.read();
|
||||||
|
if (r >= 0) {
|
||||||
|
if (isPeek) {
|
||||||
|
c = r;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
checkInterrupted();
|
||||||
|
long cur = System.currentTimeMillis();
|
||||||
|
if (timeout > 0 && cur - start > timeout) {
|
||||||
|
return NonBlockingInputStream.READ_EXPIRED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setNonBlocking() {
|
||||||
|
if (current == null
|
||||||
|
|| current.getControlChar(Attributes.ControlChar.VMIN) != 0
|
||||||
|
|| current.getControlChar(Attributes.ControlChar.VTIME) != 1) {
|
||||||
|
try {
|
||||||
|
Attributes attr = getAttr();
|
||||||
|
attr.setControlChar(Attributes.ControlChar.VMIN, 0);
|
||||||
|
attr.setControlChar(Attributes.ControlChar.VTIME, 1);
|
||||||
|
setAttr(attr);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,292 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2021, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Attributes.ControlChar;
|
||||||
|
import org.jline.terminal.Attributes.InputFlag;
|
||||||
|
import org.jline.terminal.Attributes.LocalFlag;
|
||||||
|
import org.jline.terminal.Cursor;
|
||||||
|
import org.jline.terminal.MouseEvent;
|
||||||
|
import org.jline.terminal.spi.TerminalExt;
|
||||||
|
import org.jline.utils.ColorPalette;
|
||||||
|
import org.jline.utils.Curses;
|
||||||
|
import org.jline.utils.InfoCmp;
|
||||||
|
import org.jline.utils.InfoCmp.Capability;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
import org.jline.utils.Status;
|
||||||
|
|
||||||
|
public abstract class AbstractTerminal implements TerminalExt {
|
||||||
|
|
||||||
|
protected final String name;
|
||||||
|
protected final String type;
|
||||||
|
protected final Charset encoding;
|
||||||
|
protected final Map<Signal, SignalHandler> handlers = new ConcurrentHashMap<>();
|
||||||
|
protected final Set<Capability> bools = new HashSet<>();
|
||||||
|
protected final Map<Capability, Integer> ints = new HashMap<>();
|
||||||
|
protected final Map<Capability, String> strings = new HashMap<>();
|
||||||
|
protected final ColorPalette palette;
|
||||||
|
protected Status status;
|
||||||
|
protected Runnable onClose;
|
||||||
|
private MouseEvent lastMouseEvent = new MouseEvent(
|
||||||
|
MouseEvent.Type.Moved, MouseEvent.Button.NoButton, EnumSet.noneOf(MouseEvent.Modifier.class), 0, 0);
|
||||||
|
|
||||||
|
public AbstractTerminal(String name, String type) throws IOException {
|
||||||
|
this(name, type, null, SignalHandler.SIG_DFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public AbstractTerminal(String name, String type, Charset encoding, SignalHandler signalHandler)
|
||||||
|
throws IOException {
|
||||||
|
this.name = name;
|
||||||
|
this.type = type != null ? type : "ansi";
|
||||||
|
this.encoding = encoding != null ? encoding : Charset.defaultCharset();
|
||||||
|
this.palette = new ColorPalette(this);
|
||||||
|
for (Signal signal : Signal.values()) {
|
||||||
|
handlers.put(signal, signalHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnClose(Runnable onClose) {
|
||||||
|
this.onClose = onClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status getStatus() {
|
||||||
|
return getStatus(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status getStatus(boolean create) {
|
||||||
|
if (status == null && create) {
|
||||||
|
status = new Status(this);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignalHandler handle(Signal signal, SignalHandler handler) {
|
||||||
|
Objects.requireNonNull(signal);
|
||||||
|
Objects.requireNonNull(handler);
|
||||||
|
return handlers.put(signal, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void raise(Signal signal) {
|
||||||
|
Objects.requireNonNull(signal);
|
||||||
|
SignalHandler handler = handlers.get(signal);
|
||||||
|
if (handler == SignalHandler.SIG_DFL) {
|
||||||
|
if (status != null && signal == Signal.WINCH) {
|
||||||
|
status.resize();
|
||||||
|
}
|
||||||
|
} else if (handler != SignalHandler.SIG_IGN) {
|
||||||
|
handler.handle(signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void close() throws IOException {
|
||||||
|
try {
|
||||||
|
doClose();
|
||||||
|
} finally {
|
||||||
|
if (onClose != null) {
|
||||||
|
onClose.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doClose() throws IOException {
|
||||||
|
if (status != null) {
|
||||||
|
status.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void echoSignal(Signal signal) {
|
||||||
|
ControlChar cc = null;
|
||||||
|
switch (signal) {
|
||||||
|
case INT:
|
||||||
|
cc = ControlChar.VINTR;
|
||||||
|
break;
|
||||||
|
case QUIT:
|
||||||
|
cc = ControlChar.VQUIT;
|
||||||
|
break;
|
||||||
|
case TSTP:
|
||||||
|
cc = ControlChar.VSUSP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cc != null) {
|
||||||
|
int vcc = getAttributes().getControlChar(cc);
|
||||||
|
if (vcc > 0 && vcc < 32) {
|
||||||
|
writer().write(new char[]{'^', (char) (vcc + '@')}, 0, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attributes enterRawMode() {
|
||||||
|
Attributes prvAttr = getAttributes();
|
||||||
|
Attributes newAttr = new Attributes(prvAttr);
|
||||||
|
newAttr.setLocalFlags(EnumSet.of(LocalFlag.ICANON, LocalFlag.ECHO, LocalFlag.IEXTEN), false);
|
||||||
|
newAttr.setInputFlags(EnumSet.of(InputFlag.IXON, InputFlag.ICRNL, InputFlag.INLCR), false);
|
||||||
|
newAttr.setControlChar(ControlChar.VMIN, 0);
|
||||||
|
newAttr.setControlChar(ControlChar.VTIME, 1);
|
||||||
|
setAttributes(newAttr);
|
||||||
|
return prvAttr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean echo() {
|
||||||
|
return getAttributes().getLocalFlag(LocalFlag.ECHO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean echo(boolean echo) {
|
||||||
|
Attributes attr = getAttributes();
|
||||||
|
boolean prev = attr.getLocalFlag(LocalFlag.ECHO);
|
||||||
|
if (prev != echo) {
|
||||||
|
attr.setLocalFlag(LocalFlag.ECHO, echo);
|
||||||
|
setAttributes(attr);
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKind() {
|
||||||
|
return getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Charset encoding() {
|
||||||
|
return this.encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush() {
|
||||||
|
writer().flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean puts(Capability capability, Object... params) {
|
||||||
|
String str = getStringCapability(capability);
|
||||||
|
if (str == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Curses.tputs(writer(), str, params);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBooleanCapability(Capability capability) {
|
||||||
|
return bools.contains(capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNumericCapability(Capability capability) {
|
||||||
|
return ints.get(capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStringCapability(Capability capability) {
|
||||||
|
return strings.get(capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parseInfoCmp() {
|
||||||
|
String capabilities = null;
|
||||||
|
try {
|
||||||
|
capabilities = InfoCmp.getInfoCmp(type);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.warn("Unable to retrieve infocmp for type " + type, e);
|
||||||
|
}
|
||||||
|
if (capabilities == null) {
|
||||||
|
capabilities = InfoCmp.getLoadedInfoCmp("ansi");
|
||||||
|
}
|
||||||
|
InfoCmp.parseInfoCmp(capabilities, bools, ints, strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor getCursorPosition(IntConsumer discarded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMouseSupport() {
|
||||||
|
return MouseSupport.hasMouseSupport(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trackMouse(MouseTracking tracking) {
|
||||||
|
return MouseSupport.trackMouse(this, tracking);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MouseEvent readMouseEvent() {
|
||||||
|
return lastMouseEvent = MouseSupport.readMouse(this, lastMouseEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MouseEvent readMouseEvent(IntSupplier reader) {
|
||||||
|
return lastMouseEvent = MouseSupport.readMouse(reader, lastMouseEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFocusSupport() {
|
||||||
|
return type.startsWith("xterm");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trackFocus(boolean tracking) {
|
||||||
|
if (hasFocusSupport()) {
|
||||||
|
writer().write(tracking ? "\033[?1004h" : "\033[?1004l");
|
||||||
|
writer().flush();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkInterrupted() throws InterruptedIOException {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new InterruptedIOException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canPauseResume() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause(boolean wait) throws InterruptedException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resume() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean paused() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColorPalette getPalette() {
|
||||||
|
return palette;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2017, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
public abstract class AbstractWindowsConsoleWriter extends Writer {
|
||||||
|
|
||||||
|
protected abstract void writeConsole(char[] text, int len) throws IOException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(char[] cbuf, int off, int len) throws IOException {
|
||||||
|
char[] text = cbuf;
|
||||||
|
if (off != 0) {
|
||||||
|
text = new char[len];
|
||||||
|
System.arraycopy(cbuf, off, text, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this.lock) {
|
||||||
|
writeConsole(text, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,554 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2023, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Size;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
import org.jline.utils.Curses;
|
||||||
|
import org.jline.utils.InfoCmp;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
import org.jline.utils.NonBlocking;
|
||||||
|
import org.jline.utils.NonBlockingInputStream;
|
||||||
|
import org.jline.utils.NonBlockingPumpReader;
|
||||||
|
import org.jline.utils.NonBlockingReader;
|
||||||
|
import org.jline.utils.ShutdownHooks;
|
||||||
|
import org.jline.utils.Signals;
|
||||||
|
import org.jline.utils.WriterOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AbstractWindowsTerminal is used as the base class for windows terminal.
|
||||||
|
* Due to windows limitations, mostly the missing support for ansi sequences,
|
||||||
|
* the only way to create a correct terminal is to use the windows api to set
|
||||||
|
* character attributes, move the cursor, erasing, etc...
|
||||||
|
* <p>
|
||||||
|
* UTF-8 support is also lacking in windows and the code page supposed to
|
||||||
|
* emulate UTF-8 is a bit broken. In order to work around this broken
|
||||||
|
* code page, windows api WriteConsoleW is used directly. This means that
|
||||||
|
* the writer() becomes the primary output, while the output() is bridged
|
||||||
|
* to the writer() using a WriterOutputStream wrapper.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractWindowsTerminal<Console> extends AbstractTerminal {
|
||||||
|
|
||||||
|
public static final String TYPE_WINDOWS = "windows";
|
||||||
|
public static final String TYPE_WINDOWS_256_COLOR = "windows-256color";
|
||||||
|
public static final String TYPE_WINDOWS_CONEMU = "windows-conemu";
|
||||||
|
public static final String TYPE_WINDOWS_VTP = "windows-vtp";
|
||||||
|
|
||||||
|
public static final int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
|
||||||
|
protected static final int ENABLE_PROCESSED_INPUT = 0x0001;
|
||||||
|
protected static final int ENABLE_LINE_INPUT = 0x0002;
|
||||||
|
protected static final int ENABLE_ECHO_INPUT = 0x0004;
|
||||||
|
protected static final int ENABLE_WINDOW_INPUT = 0x0008;
|
||||||
|
protected static final int ENABLE_MOUSE_INPUT = 0x0010;
|
||||||
|
protected static final int ENABLE_INSERT_MODE = 0x0020;
|
||||||
|
protected static final int ENABLE_QUICK_EDIT_MODE = 0x0040;
|
||||||
|
protected static final int ENABLE_EXTENDED_FLAGS = 0x0080;
|
||||||
|
static final int SHIFT_FLAG = 0x01;
|
||||||
|
static final int ALT_FLAG = 0x02;
|
||||||
|
static final int CTRL_FLAG = 0x04;
|
||||||
|
static final int RIGHT_ALT_PRESSED = 0x0001;
|
||||||
|
static final int LEFT_ALT_PRESSED = 0x0002;
|
||||||
|
static final int RIGHT_CTRL_PRESSED = 0x0004;
|
||||||
|
static final int LEFT_CTRL_PRESSED = 0x0008;
|
||||||
|
static final int SHIFT_PRESSED = 0x0010;
|
||||||
|
static final int NUMLOCK_ON = 0x0020;
|
||||||
|
static final int SCROLLLOCK_ON = 0x0040;
|
||||||
|
static final int CAPSLOCK_ON = 0x0080;
|
||||||
|
private static final int UTF8_CODE_PAGE = 65001;
|
||||||
|
protected final Writer slaveInputPipe;
|
||||||
|
protected final NonBlockingInputStream input;
|
||||||
|
protected final OutputStream output;
|
||||||
|
protected final NonBlockingReader reader;
|
||||||
|
protected final PrintWriter writer;
|
||||||
|
protected final Map<Signal, Object> nativeHandlers = new HashMap<>();
|
||||||
|
protected final ShutdownHooks.Task closer;
|
||||||
|
protected final Attributes attributes = new Attributes();
|
||||||
|
protected final Console inConsole;
|
||||||
|
protected final Console outConsole;
|
||||||
|
protected final int originalInConsoleMode;
|
||||||
|
protected final int originalOutConsoleMode;
|
||||||
|
protected final Object lock = new Object();
|
||||||
|
private final TerminalProvider provider;
|
||||||
|
private final SystemStream systemStream;
|
||||||
|
protected boolean paused = true;
|
||||||
|
protected Thread pump;
|
||||||
|
protected MouseTracking tracking = MouseTracking.Off;
|
||||||
|
protected boolean focusTracking = false;
|
||||||
|
protected boolean skipNextLf;
|
||||||
|
private volatile boolean closing;
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public AbstractWindowsTerminal(
|
||||||
|
TerminalProvider provider,
|
||||||
|
SystemStream systemStream,
|
||||||
|
Writer writer,
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
Charset encoding,
|
||||||
|
boolean nativeSignals,
|
||||||
|
SignalHandler signalHandler,
|
||||||
|
Console inConsole,
|
||||||
|
int inConsoleMode,
|
||||||
|
Console outConsole,
|
||||||
|
int outConsoleMode)
|
||||||
|
throws IOException {
|
||||||
|
super(name, type, encoding, signalHandler);
|
||||||
|
this.provider = provider;
|
||||||
|
this.systemStream = systemStream;
|
||||||
|
NonBlockingPumpReader reader = NonBlocking.nonBlockingPumpReader();
|
||||||
|
this.slaveInputPipe = reader.getWriter();
|
||||||
|
this.reader = reader;
|
||||||
|
this.input = NonBlocking.nonBlockingStream(reader, encoding());
|
||||||
|
this.writer = new PrintWriter(writer);
|
||||||
|
this.output = new WriterOutputStream(writer, encoding());
|
||||||
|
this.inConsole = inConsole;
|
||||||
|
this.outConsole = outConsole;
|
||||||
|
parseInfoCmp();
|
||||||
|
// Attributes
|
||||||
|
this.originalInConsoleMode = inConsoleMode;
|
||||||
|
this.originalOutConsoleMode = outConsoleMode;
|
||||||
|
attributes.setLocalFlag(Attributes.LocalFlag.ISIG, true);
|
||||||
|
attributes.setControlChar(Attributes.ControlChar.VINTR, ctrl('C'));
|
||||||
|
attributes.setControlChar(Attributes.ControlChar.VEOF, ctrl('D'));
|
||||||
|
attributes.setControlChar(Attributes.ControlChar.VSUSP, ctrl('Z'));
|
||||||
|
// Handle signals
|
||||||
|
if (nativeSignals) {
|
||||||
|
for (final Signal signal : Signal.values()) {
|
||||||
|
if (signalHandler == SignalHandler.SIG_DFL) {
|
||||||
|
nativeHandlers.put(signal, Signals.registerDefault(signal.name()));
|
||||||
|
} else {
|
||||||
|
nativeHandlers.put(signal, Signals.register(signal.name(), () -> raise(signal)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closer = this::close;
|
||||||
|
ShutdownHooks.add(closer);
|
||||||
|
// ConEMU extended fonts support
|
||||||
|
if (TYPE_WINDOWS_CONEMU.equals(getType())
|
||||||
|
&& !Boolean.getBoolean("org.jline.terminal.conemu.disable-activate")) {
|
||||||
|
writer.write("\u001b[9999E");
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SignalHandler handle(Signal signal, SignalHandler handler) {
|
||||||
|
SignalHandler prev = super.handle(signal, handler);
|
||||||
|
if (prev != handler) {
|
||||||
|
if (handler == SignalHandler.SIG_DFL) {
|
||||||
|
Signals.registerDefault(signal.name());
|
||||||
|
} else {
|
||||||
|
Signals.register(signal.name(), () -> raise(signal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonBlockingReader reader() {
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintWriter writer() {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream input() {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream output() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attributes getAttributes() {
|
||||||
|
int mode = getConsoleMode(inConsole);
|
||||||
|
if ((mode & ENABLE_ECHO_INPUT) != 0) {
|
||||||
|
attributes.setLocalFlag(Attributes.LocalFlag.ECHO, true);
|
||||||
|
}
|
||||||
|
if ((mode & ENABLE_LINE_INPUT) != 0) {
|
||||||
|
attributes.setLocalFlag(Attributes.LocalFlag.ICANON, true);
|
||||||
|
}
|
||||||
|
return new Attributes(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Attributes attr) {
|
||||||
|
attributes.copy(attr);
|
||||||
|
updateConsoleMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateConsoleMode() {
|
||||||
|
int mode = ENABLE_WINDOW_INPUT;
|
||||||
|
if (attributes.getLocalFlag(Attributes.LocalFlag.ISIG)) {
|
||||||
|
mode |= ENABLE_PROCESSED_INPUT;
|
||||||
|
}
|
||||||
|
if (attributes.getLocalFlag(Attributes.LocalFlag.ECHO)) {
|
||||||
|
mode |= ENABLE_ECHO_INPUT;
|
||||||
|
}
|
||||||
|
if (attributes.getLocalFlag(Attributes.LocalFlag.ICANON)) {
|
||||||
|
mode |= ENABLE_LINE_INPUT;
|
||||||
|
}
|
||||||
|
if (tracking != MouseTracking.Off) {
|
||||||
|
mode |= ENABLE_MOUSE_INPUT;
|
||||||
|
// mouse events not send with quick edit mode
|
||||||
|
// to disable ENABLE_QUICK_EDIT_MODE just set extended flag
|
||||||
|
mode |= ENABLE_EXTENDED_FLAGS;
|
||||||
|
}
|
||||||
|
setConsoleMode(inConsole, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int ctrl(char key) {
|
||||||
|
return (Character.toUpperCase(key) & 0x1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(Size size) {
|
||||||
|
throw new UnsupportedOperationException("Can not resize windows terminal");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doClose() throws IOException {
|
||||||
|
super.doClose();
|
||||||
|
closing = true;
|
||||||
|
if (pump != null) {
|
||||||
|
pump.interrupt();
|
||||||
|
}
|
||||||
|
ShutdownHooks.remove(closer);
|
||||||
|
for (Map.Entry<Signal, Object> entry : nativeHandlers.entrySet()) {
|
||||||
|
Signals.unregister(entry.getKey().name(), entry.getValue());
|
||||||
|
}
|
||||||
|
reader.close();
|
||||||
|
writer.close();
|
||||||
|
setConsoleMode(inConsole, originalInConsoleMode);
|
||||||
|
setConsoleMode(outConsole, originalOutConsoleMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processKeyEvent(
|
||||||
|
final boolean isKeyDown, final short virtualKeyCode, char ch, final int controlKeyState)
|
||||||
|
throws IOException {
|
||||||
|
final boolean isCtrl = (controlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) > 0;
|
||||||
|
final boolean isAlt = (controlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) > 0;
|
||||||
|
final boolean isShift = (controlKeyState & SHIFT_PRESSED) > 0;
|
||||||
|
// key down event
|
||||||
|
if (isKeyDown && ch != '\3') {
|
||||||
|
// Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
|
||||||
|
// otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
|
||||||
|
if (ch != 0
|
||||||
|
&& (controlKeyState
|
||||||
|
& (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
|
||||||
|
== (RIGHT_ALT_PRESSED | LEFT_CTRL_PRESSED)) {
|
||||||
|
processInputChar(ch);
|
||||||
|
} else {
|
||||||
|
final String keySeq = getEscapeSequence(
|
||||||
|
virtualKeyCode, (isCtrl ? CTRL_FLAG : 0) + (isAlt ? ALT_FLAG : 0) + (isShift ? SHIFT_FLAG : 0));
|
||||||
|
if (keySeq != null) {
|
||||||
|
for (char c : keySeq.toCharArray()) {
|
||||||
|
processInputChar(c);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* uchar value in Windows when CTRL is pressed:
|
||||||
|
* 1). Ctrl + <0x41 to 0x5e> : uchar=<keyCode> - 'A' + 1
|
||||||
|
* 2). Ctrl + Backspace(0x08) : uchar=0x7f
|
||||||
|
* 3). Ctrl + Enter(0x0d) : uchar=0x0a
|
||||||
|
* 4). Ctrl + Space(0x20) : uchar=0x20
|
||||||
|
* 5). Ctrl + <Other key> : uchar=0
|
||||||
|
* 6). Ctrl + Alt + <Any key> : uchar=0
|
||||||
|
*/
|
||||||
|
if (ch > 0) {
|
||||||
|
if (isAlt) {
|
||||||
|
processInputChar('\033');
|
||||||
|
}
|
||||||
|
if (isCtrl && ch != ' ' && ch != '\n' && ch != 0x7f) {
|
||||||
|
processInputChar((char) (ch == '?' ? 0x7f : Character.toUpperCase(ch) & 0x1f));
|
||||||
|
} else {
|
||||||
|
processInputChar(ch);
|
||||||
|
}
|
||||||
|
} else if (isCtrl) { // Handles the ctrl key events(uchar=0)
|
||||||
|
if (virtualKeyCode >= 'A' && virtualKeyCode <= 'Z') {
|
||||||
|
ch = (char) (virtualKeyCode - 0x40);
|
||||||
|
} else if (virtualKeyCode == 191) { // ?
|
||||||
|
ch = 127;
|
||||||
|
}
|
||||||
|
if (ch > 0) {
|
||||||
|
if (isAlt) {
|
||||||
|
processInputChar('\033');
|
||||||
|
}
|
||||||
|
processInputChar(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isKeyDown && ch == '\3') {
|
||||||
|
processInputChar('\3');
|
||||||
|
}
|
||||||
|
// key up event
|
||||||
|
else {
|
||||||
|
// support ALT+NumPad input method
|
||||||
|
if (virtualKeyCode == 0x12 /*VK_MENU ALT key*/ && ch > 0) {
|
||||||
|
processInputChar(ch); // no such combination in Windows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getEscapeSequence(short keyCode, int keyState) {
|
||||||
|
// virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||||
|
// TODO: numpad keys, modifiers
|
||||||
|
String escapeSequence = null;
|
||||||
|
switch (keyCode) {
|
||||||
|
case 0x08: // VK_BACK BackSpace
|
||||||
|
escapeSequence = (keyState & ALT_FLAG) > 0 ? "\\E^H" : getRawSequence(InfoCmp.Capability.key_backspace);
|
||||||
|
break;
|
||||||
|
case 0x09:
|
||||||
|
escapeSequence = (keyState & SHIFT_FLAG) > 0 ? getRawSequence(InfoCmp.Capability.key_btab) : null;
|
||||||
|
break;
|
||||||
|
case 0x21: // VK_PRIOR PageUp
|
||||||
|
escapeSequence = getRawSequence(InfoCmp.Capability.key_ppage);
|
||||||
|
break;
|
||||||
|
case 0x22: // VK_NEXT PageDown
|
||||||
|
escapeSequence = getRawSequence(InfoCmp.Capability.key_npage);
|
||||||
|
break;
|
||||||
|
case 0x23: // VK_END
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dF" : getRawSequence(InfoCmp.Capability.key_end);
|
||||||
|
break;
|
||||||
|
case 0x24: // VK_HOME
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dH" : getRawSequence(InfoCmp.Capability.key_home);
|
||||||
|
break;
|
||||||
|
case 0x25: // VK_LEFT
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dD" : getRawSequence(InfoCmp.Capability.key_left);
|
||||||
|
break;
|
||||||
|
case 0x26: // VK_UP
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dA" : getRawSequence(InfoCmp.Capability.key_up);
|
||||||
|
break;
|
||||||
|
case 0x27: // VK_RIGHT
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dC" : getRawSequence(InfoCmp.Capability.key_right);
|
||||||
|
break;
|
||||||
|
case 0x28: // VK_DOWN
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dB" : getRawSequence(InfoCmp.Capability.key_down);
|
||||||
|
break;
|
||||||
|
case 0x2D: // VK_INSERT
|
||||||
|
escapeSequence = getRawSequence(InfoCmp.Capability.key_ic);
|
||||||
|
break;
|
||||||
|
case 0x2E: // VK_DELETE
|
||||||
|
escapeSequence = getRawSequence(InfoCmp.Capability.key_dc);
|
||||||
|
break;
|
||||||
|
case 0x70: // VK_F1
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dP" : getRawSequence(InfoCmp.Capability.key_f1);
|
||||||
|
break;
|
||||||
|
case 0x71: // VK_F2
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dQ" : getRawSequence(InfoCmp.Capability.key_f2);
|
||||||
|
break;
|
||||||
|
case 0x72: // VK_F3
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dR" : getRawSequence(InfoCmp.Capability.key_f3);
|
||||||
|
break;
|
||||||
|
case 0x73: // VK_F4
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[1;%p1%dS" : getRawSequence(InfoCmp.Capability.key_f4);
|
||||||
|
break;
|
||||||
|
case 0x74: // VK_F5
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[15;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f5);
|
||||||
|
break;
|
||||||
|
case 0x75: // VK_F6
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[17;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f6);
|
||||||
|
break;
|
||||||
|
case 0x76: // VK_F7
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[18;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f7);
|
||||||
|
break;
|
||||||
|
case 0x77: // VK_F8
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[19;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f8);
|
||||||
|
break;
|
||||||
|
case 0x78: // VK_F9
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[20;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f9);
|
||||||
|
break;
|
||||||
|
case 0x79: // VK_F10
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[21;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f10);
|
||||||
|
break;
|
||||||
|
case 0x7A: // VK_F11
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[23;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f11);
|
||||||
|
break;
|
||||||
|
case 0x7B: // VK_F12
|
||||||
|
escapeSequence = keyState > 0 ? "\\E[24;%p1%d~" : getRawSequence(InfoCmp.Capability.key_f12);
|
||||||
|
break;
|
||||||
|
case 0x5D: // VK_CLOSE_BRACKET(Menu key)
|
||||||
|
case 0x5B: // VK_OPEN_BRACKET(Window key)
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Curses.tputs(escapeSequence, keyState + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getRawSequence(InfoCmp.Capability cap) {
|
||||||
|
return strings.get(cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFocusSupport() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trackFocus(boolean tracking) {
|
||||||
|
focusTracking = tracking;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canPauseResume() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
synchronized (lock) {
|
||||||
|
paused = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause(boolean wait) throws InterruptedException {
|
||||||
|
Thread p;
|
||||||
|
synchronized (lock) {
|
||||||
|
paused = true;
|
||||||
|
p = pump;
|
||||||
|
}
|
||||||
|
if (p != null) {
|
||||||
|
p.interrupt();
|
||||||
|
p.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resume() {
|
||||||
|
synchronized (lock) {
|
||||||
|
paused = false;
|
||||||
|
if (pump == null) {
|
||||||
|
pump = new Thread(this::pump, "WindowsStreamPump");
|
||||||
|
pump.setDaemon(true);
|
||||||
|
pump.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean paused() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void pump() {
|
||||||
|
try {
|
||||||
|
while (!closing) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (paused) {
|
||||||
|
pump = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (processConsoleInput()) {
|
||||||
|
slaveInputPipe.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (!closing) {
|
||||||
|
Log.warn("Error in WindowsStreamPump", e);
|
||||||
|
try {
|
||||||
|
close();
|
||||||
|
} catch (IOException e1) {
|
||||||
|
Log.warn("Error closing terminal", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
synchronized (lock) {
|
||||||
|
pump = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processInputChar(char c) throws IOException {
|
||||||
|
if (attributes.getLocalFlag(Attributes.LocalFlag.ISIG)) {
|
||||||
|
if (c == attributes.getControlChar(Attributes.ControlChar.VINTR)) {
|
||||||
|
raise(Signal.INT);
|
||||||
|
return;
|
||||||
|
} else if (c == attributes.getControlChar(Attributes.ControlChar.VQUIT)) {
|
||||||
|
raise(Signal.QUIT);
|
||||||
|
return;
|
||||||
|
} else if (c == attributes.getControlChar(Attributes.ControlChar.VSUSP)) {
|
||||||
|
raise(Signal.TSTP);
|
||||||
|
return;
|
||||||
|
} else if (c == attributes.getControlChar(Attributes.ControlChar.VSTATUS)) {
|
||||||
|
raise(Signal.INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attributes.getInputFlag(Attributes.InputFlag.INORMEOL)) {
|
||||||
|
if (c == '\r') {
|
||||||
|
skipNextLf = true;
|
||||||
|
c = '\n';
|
||||||
|
} else if (c == '\n') {
|
||||||
|
if (skipNextLf) {
|
||||||
|
skipNextLf = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
skipNextLf = false;
|
||||||
|
}
|
||||||
|
} else if (c == '\r') {
|
||||||
|
if (attributes.getInputFlag(Attributes.InputFlag.IGNCR)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (attributes.getInputFlag(Attributes.InputFlag.ICRNL)) {
|
||||||
|
c = '\n';
|
||||||
|
}
|
||||||
|
} else if (c == '\n' && attributes.getInputFlag(Attributes.InputFlag.INLCR)) {
|
||||||
|
c = '\r';
|
||||||
|
}
|
||||||
|
// if (attributes.getLocalFlag(Attributes.LocalFlag.ECHO)) {
|
||||||
|
// processOutputByte(c);
|
||||||
|
// masterOutput.flush();
|
||||||
|
// }
|
||||||
|
slaveInputPipe.write(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trackMouse(MouseTracking tracking) {
|
||||||
|
this.tracking = tracking;
|
||||||
|
updateConsoleMode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract int getConsoleMode(Console console);
|
||||||
|
|
||||||
|
protected abstract void setConsoleMode(Console console, int mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a single input event from the input buffer and process it.
|
||||||
|
*
|
||||||
|
* @return true if new input was generated from the event
|
||||||
|
* @throws IOException if anything wrong happens
|
||||||
|
*/
|
||||||
|
protected abstract boolean processConsoleInput() throws IOException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalProvider getProvider() {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SystemStream getSystemStream() {
|
||||||
|
return systemStream;
|
||||||
|
}
|
||||||
|
}
|
108
net-cli/src/main/java/org/jline/terminal/impl/CursorSupport.java
Normal file
108
net-cli/src/main/java/org/jline/terminal/impl/CursorSupport.java
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOError;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.jline.terminal.Cursor;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.utils.Curses;
|
||||||
|
import org.jline.utils.InfoCmp;
|
||||||
|
|
||||||
|
public class CursorSupport {
|
||||||
|
|
||||||
|
public static Cursor getCursorPosition(Terminal terminal, IntConsumer discarded) {
|
||||||
|
try {
|
||||||
|
String u6 = terminal.getStringCapability(InfoCmp.Capability.user6);
|
||||||
|
String u7 = terminal.getStringCapability(InfoCmp.Capability.user7);
|
||||||
|
if (u6 == null || u7 == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Prepare parser
|
||||||
|
boolean inc1 = false;
|
||||||
|
StringBuilder patb = new StringBuilder();
|
||||||
|
int index = 0;
|
||||||
|
while (index < u6.length()) {
|
||||||
|
char ch;
|
||||||
|
switch (ch = u6.charAt(index++)) {
|
||||||
|
case '\\':
|
||||||
|
switch (u6.charAt(index++)) {
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
patb.append("\\x1b");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
ch = u6.charAt(index++);
|
||||||
|
switch (ch) {
|
||||||
|
case '%':
|
||||||
|
patb.append('%');
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
inc1 = true;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
patb.append("([0-9]+)");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
switch (ch) {
|
||||||
|
case '[':
|
||||||
|
patb.append('\\');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
patb.append(ch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Pattern pattern = Pattern.compile(patb.toString());
|
||||||
|
// Output cursor position request
|
||||||
|
Curses.tputs(terminal.writer(), u7);
|
||||||
|
terminal.flush();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int start = 0;
|
||||||
|
while (true) {
|
||||||
|
int c = terminal.reader().read();
|
||||||
|
if (c < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
sb.append((char) c);
|
||||||
|
Matcher matcher = pattern.matcher(sb.substring(start));
|
||||||
|
if (matcher.matches()) {
|
||||||
|
int y = Integer.parseInt(matcher.group(1));
|
||||||
|
int x = Integer.parseInt(matcher.group(2));
|
||||||
|
if (inc1) {
|
||||||
|
x--;
|
||||||
|
y--;
|
||||||
|
}
|
||||||
|
if (discarded != null) {
|
||||||
|
for (int i = 0; i < start; i++) {
|
||||||
|
discarded.accept(sb.charAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Cursor(x, y);
|
||||||
|
} else if (!matcher.hitEnd()) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
194
net-cli/src/main/java/org/jline/terminal/impl/Diag.java
Normal file
194
net-cli/src/main/java/org/jline/terminal/impl/Diag.java
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
import java.util.concurrent.ForkJoinTask;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
import org.jline.utils.OSUtils;
|
||||||
|
|
||||||
|
public class Diag {
|
||||||
|
|
||||||
|
private final PrintStream out;
|
||||||
|
private final boolean verbose;
|
||||||
|
|
||||||
|
public Diag(PrintStream out, boolean verbose) {
|
||||||
|
this.out = out;
|
||||||
|
this.verbose = verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
diag(System.out, Arrays.asList(args).contains("--verbose"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void diag(PrintStream out) {
|
||||||
|
diag(out, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void diag(PrintStream out, boolean verbose) {
|
||||||
|
new Diag(out, verbose).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
out.println("System properties");
|
||||||
|
out.println("=================");
|
||||||
|
out.println("os.name = " + System.getProperty("os.name"));
|
||||||
|
out.println("OSTYPE = " + System.getenv("OSTYPE"));
|
||||||
|
out.println("MSYSTEM = " + System.getenv("MSYSTEM"));
|
||||||
|
out.println("PWD = " + System.getenv("PWD"));
|
||||||
|
out.println("ConEmuPID = " + System.getenv("ConEmuPID"));
|
||||||
|
out.println("WSL_DISTRO_NAME = " + System.getenv("WSL_DISTRO_NAME"));
|
||||||
|
out.println("WSL_INTEROP = " + System.getenv("WSL_INTEROP"));
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("OSUtils");
|
||||||
|
out.println("=================");
|
||||||
|
out.println("IS_WINDOWS = " + OSUtils.IS_WINDOWS);
|
||||||
|
out.println("IS_CYGWIN = " + OSUtils.IS_CYGWIN);
|
||||||
|
out.println("IS_MSYSTEM = " + OSUtils.IS_MSYSTEM);
|
||||||
|
out.println("IS_WSL = " + OSUtils.IS_WSL);
|
||||||
|
out.println("IS_WSL1 = " + OSUtils.IS_WSL1);
|
||||||
|
out.println("IS_WSL2 = " + OSUtils.IS_WSL2);
|
||||||
|
out.println("IS_CONEMU = " + OSUtils.IS_CONEMU);
|
||||||
|
out.println("IS_OSX = " + OSUtils.IS_OSX);
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
// FFM
|
||||||
|
out.println("FFM Support");
|
||||||
|
out.println("=================");
|
||||||
|
try {
|
||||||
|
TerminalProvider provider = TerminalProvider.load("ffm");
|
||||||
|
testProvider(provider);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
error("FFM support not available", t);
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("JnaSupport");
|
||||||
|
out.println("=================");
|
||||||
|
try {
|
||||||
|
TerminalProvider provider = TerminalProvider.load("jna");
|
||||||
|
testProvider(provider);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
error("JNA support not available", t);
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("Jansi2Support");
|
||||||
|
out.println("=================");
|
||||||
|
try {
|
||||||
|
TerminalProvider provider = TerminalProvider.load("jansi");
|
||||||
|
testProvider(provider);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
error("Jansi 2 support not available", t);
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("JniSupport");
|
||||||
|
out.println("=================");
|
||||||
|
try {
|
||||||
|
TerminalProvider provider = TerminalProvider.load("jni");
|
||||||
|
testProvider(provider);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
error("JNI support not available", t);
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
// Exec
|
||||||
|
out.println("Exec Support");
|
||||||
|
out.println("=================");
|
||||||
|
try {
|
||||||
|
TerminalProvider provider = TerminalProvider.load("exec");
|
||||||
|
testProvider(provider);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
error("Exec support not available", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!verbose) {
|
||||||
|
out.println();
|
||||||
|
out.println("Run with --verbose argument to print stack traces");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testProvider(TerminalProvider provider) {
|
||||||
|
try {
|
||||||
|
out.println("StdIn stream = " + provider.isSystemStream(SystemStream.Input));
|
||||||
|
out.println("StdOut stream = " + provider.isSystemStream(SystemStream.Output));
|
||||||
|
out.println("StdErr stream = " + provider.isSystemStream(SystemStream.Error));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
error("Unable to check stream", t);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
out.println("StdIn stream name = " + provider.systemStreamName(SystemStream.Input));
|
||||||
|
out.println("StdOut stream name = " + provider.systemStreamName(SystemStream.Output));
|
||||||
|
out.println("StdErr stream name = " + provider.systemStreamName(SystemStream.Error));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
error("Unable to check stream names", t);
|
||||||
|
}
|
||||||
|
try (Terminal terminal = provider.sysTerminal(
|
||||||
|
"diag",
|
||||||
|
"xterm",
|
||||||
|
false,
|
||||||
|
StandardCharsets.UTF_8,
|
||||||
|
false,
|
||||||
|
Terminal.SignalHandler.SIG_DFL,
|
||||||
|
false,
|
||||||
|
SystemStream.Output)) {
|
||||||
|
if (terminal != null) {
|
||||||
|
Attributes attr = terminal.enterRawMode();
|
||||||
|
try {
|
||||||
|
out.println("Terminal size: " + terminal.getSize());
|
||||||
|
ForkJoinPool forkJoinPool = new ForkJoinPool(1);
|
||||||
|
try {
|
||||||
|
ForkJoinTask<Integer> t =
|
||||||
|
forkJoinPool.submit(() -> terminal.reader().read(1));
|
||||||
|
t.get(1000, TimeUnit.MILLISECONDS);
|
||||||
|
} finally {
|
||||||
|
forkJoinPool.shutdown();
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("The terminal seems to work: ");
|
||||||
|
sb.append("terminal ").append(terminal.getClass().getName());
|
||||||
|
if (terminal instanceof AbstractPosixTerminal) {
|
||||||
|
sb.append(" with pty ")
|
||||||
|
.append(((AbstractPosixTerminal) terminal)
|
||||||
|
.getPty()
|
||||||
|
.getClass()
|
||||||
|
.getName());
|
||||||
|
}
|
||||||
|
out.println(sb);
|
||||||
|
} catch (Throwable t2) {
|
||||||
|
error("Unable to read from terminal", t2);
|
||||||
|
} finally {
|
||||||
|
terminal.setAttributes(attr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.println("Not supported by provider");
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
error("Unable to open terminal", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void error(String message, Throwable cause) {
|
||||||
|
if (verbose) {
|
||||||
|
out.println(message);
|
||||||
|
cause.printStackTrace(out);
|
||||||
|
} else {
|
||||||
|
out.println(message + ": " + cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
165
net-cli/src/main/java/org/jline/terminal/impl/DumbTerminal.java
Normal file
165
net-cli/src/main/java/org/jline/terminal/impl/DumbTerminal.java
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Attributes.ControlChar;
|
||||||
|
import org.jline.terminal.Size;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
import org.jline.utils.NonBlocking;
|
||||||
|
import org.jline.utils.NonBlockingInputStream;
|
||||||
|
import org.jline.utils.NonBlockingReader;
|
||||||
|
|
||||||
|
public class DumbTerminal extends AbstractTerminal {
|
||||||
|
|
||||||
|
private final TerminalProvider provider;
|
||||||
|
private final SystemStream systemStream;
|
||||||
|
private final NonBlockingInputStream input;
|
||||||
|
private final OutputStream output;
|
||||||
|
private final NonBlockingReader reader;
|
||||||
|
private final PrintWriter writer;
|
||||||
|
private final Attributes attributes;
|
||||||
|
private final Size size;
|
||||||
|
private boolean skipNextLf;
|
||||||
|
|
||||||
|
public DumbTerminal(InputStream in, OutputStream out) throws IOException {
|
||||||
|
this(TYPE_DUMB, TYPE_DUMB, in, out, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DumbTerminal(String name, String type, InputStream in, OutputStream out, Charset encoding)
|
||||||
|
throws IOException {
|
||||||
|
this(null, null, name, type, in, out, encoding, SignalHandler.SIG_DFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public DumbTerminal(
|
||||||
|
TerminalProvider provider,
|
||||||
|
SystemStream systemStream,
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
InputStream in,
|
||||||
|
OutputStream out,
|
||||||
|
Charset encoding,
|
||||||
|
SignalHandler signalHandler)
|
||||||
|
throws IOException {
|
||||||
|
super(name, type, encoding, signalHandler);
|
||||||
|
this.provider = provider;
|
||||||
|
this.systemStream = systemStream;
|
||||||
|
NonBlockingInputStream nbis = NonBlocking.nonBlocking(getName(), in);
|
||||||
|
this.input = new NonBlockingInputStream() {
|
||||||
|
@Override
|
||||||
|
public int read(long timeout, boolean isPeek) throws IOException {
|
||||||
|
for (; ; ) {
|
||||||
|
int c = nbis.read(timeout, isPeek);
|
||||||
|
if (attributes.getLocalFlag(Attributes.LocalFlag.ISIG)) {
|
||||||
|
if (c == attributes.getControlChar(ControlChar.VINTR)) {
|
||||||
|
raise(Signal.INT);
|
||||||
|
continue;
|
||||||
|
} else if (c == attributes.getControlChar(ControlChar.VQUIT)) {
|
||||||
|
raise(Signal.QUIT);
|
||||||
|
continue;
|
||||||
|
} else if (c == attributes.getControlChar(ControlChar.VSUSP)) {
|
||||||
|
raise(Signal.TSTP);
|
||||||
|
continue;
|
||||||
|
} else if (c == attributes.getControlChar(ControlChar.VSTATUS)) {
|
||||||
|
raise(Signal.INFO);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attributes.getInputFlag(Attributes.InputFlag.INORMEOL)) {
|
||||||
|
if (c == '\r') {
|
||||||
|
skipNextLf = true;
|
||||||
|
c = '\n';
|
||||||
|
} else if (c == '\n') {
|
||||||
|
if (skipNextLf) {
|
||||||
|
skipNextLf = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
skipNextLf = false;
|
||||||
|
}
|
||||||
|
} else if (c == '\r') {
|
||||||
|
if (attributes.getInputFlag(Attributes.InputFlag.IGNCR)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (attributes.getInputFlag(Attributes.InputFlag.ICRNL)) {
|
||||||
|
c = '\n';
|
||||||
|
}
|
||||||
|
} else if (c == '\n' && attributes.getInputFlag(Attributes.InputFlag.INLCR)) {
|
||||||
|
c = '\r';
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.output = out;
|
||||||
|
this.reader = NonBlocking.nonBlocking(getName(), input, encoding());
|
||||||
|
this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
|
||||||
|
this.attributes = new Attributes();
|
||||||
|
this.attributes.setControlChar(ControlChar.VERASE, (char) 127);
|
||||||
|
this.attributes.setControlChar(ControlChar.VWERASE, (char) 23);
|
||||||
|
this.attributes.setControlChar(ControlChar.VKILL, (char) 21);
|
||||||
|
this.attributes.setControlChar(ControlChar.VLNEXT, (char) 22);
|
||||||
|
this.size = new Size();
|
||||||
|
parseInfoCmp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonBlockingReader reader() {
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintWriter writer() {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream input() {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream output() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attributes getAttributes() {
|
||||||
|
return new Attributes(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Attributes attr) {
|
||||||
|
attributes.copy(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Size getSize() {
|
||||||
|
Size sz = new Size();
|
||||||
|
sz.copy(size);
|
||||||
|
return sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(Size sz) {
|
||||||
|
size.copy(sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalProvider getProvider() {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SystemStream getSystemStream() {
|
||||||
|
return systemStream;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Size;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.terminal.TerminalBuilder;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
|
||||||
|
public class DumbTerminalProvider implements TerminalProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return TerminalBuilder.PROP_PROVIDER_DUMB;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Terminal sysTerminal(
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
boolean ansiPassThrough,
|
||||||
|
Charset encoding,
|
||||||
|
boolean nativeSignals,
|
||||||
|
Terminal.SignalHandler signalHandler,
|
||||||
|
boolean paused,
|
||||||
|
SystemStream systemStream)
|
||||||
|
throws IOException {
|
||||||
|
return new DumbTerminal(
|
||||||
|
this,
|
||||||
|
systemStream,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
new FileInputStream(FileDescriptor.in),
|
||||||
|
new FileOutputStream(systemStream == SystemStream.Error ? FileDescriptor.err : FileDescriptor.out),
|
||||||
|
encoding,
|
||||||
|
signalHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Terminal newTerminal(
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
InputStream masterInput,
|
||||||
|
OutputStream masterOutput,
|
||||||
|
Charset encoding,
|
||||||
|
Terminal.SignalHandler signalHandler,
|
||||||
|
boolean paused,
|
||||||
|
Attributes attributes,
|
||||||
|
Size size)
|
||||||
|
throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSystemStream(SystemStream stream) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String systemStreamName(SystemStream stream) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int systemStreamWidth(SystemStream stream) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TerminalProvider[" + name() + "]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Cursor;
|
||||||
|
import org.jline.terminal.Size;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console implementation with embedded line disciplined.
|
||||||
|
* <p>
|
||||||
|
* This terminal is well-suited for supporting incoming external
|
||||||
|
* connections, such as from the network (through telnet, ssh,
|
||||||
|
* or any kind of protocol).
|
||||||
|
* The terminal will start consuming the input in a separate thread
|
||||||
|
* to generate interruption events.
|
||||||
|
*
|
||||||
|
* @see LineDisciplineTerminal
|
||||||
|
*/
|
||||||
|
public class ExternalTerminal extends LineDisciplineTerminal {
|
||||||
|
|
||||||
|
protected final AtomicBoolean closed = new AtomicBoolean();
|
||||||
|
protected final InputStream masterInput;
|
||||||
|
protected final Object lock = new Object();
|
||||||
|
private final TerminalProvider provider;
|
||||||
|
protected boolean paused = true;
|
||||||
|
protected Thread pumpThread;
|
||||||
|
|
||||||
|
public ExternalTerminal(
|
||||||
|
String name, String type, InputStream masterInput, OutputStream masterOutput, Charset encoding)
|
||||||
|
throws IOException {
|
||||||
|
this(null, name, type, masterInput, masterOutput, encoding, SignalHandler.SIG_DFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExternalTerminal(
|
||||||
|
TerminalProvider provider,
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
InputStream masterInput,
|
||||||
|
OutputStream masterOutput,
|
||||||
|
Charset encoding,
|
||||||
|
SignalHandler signalHandler)
|
||||||
|
throws IOException {
|
||||||
|
this(provider, name, type, masterInput, masterOutput, encoding, signalHandler, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExternalTerminal(
|
||||||
|
TerminalProvider provider,
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
InputStream masterInput,
|
||||||
|
OutputStream masterOutput,
|
||||||
|
Charset encoding,
|
||||||
|
SignalHandler signalHandler,
|
||||||
|
boolean paused)
|
||||||
|
throws IOException {
|
||||||
|
this(provider, name, type, masterInput, masterOutput, encoding, signalHandler, paused, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public ExternalTerminal(
|
||||||
|
TerminalProvider provider,
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
InputStream masterInput,
|
||||||
|
OutputStream masterOutput,
|
||||||
|
Charset encoding,
|
||||||
|
SignalHandler signalHandler,
|
||||||
|
boolean paused,
|
||||||
|
Attributes attributes,
|
||||||
|
Size size)
|
||||||
|
throws IOException {
|
||||||
|
super(name, type, masterOutput, encoding, signalHandler);
|
||||||
|
this.provider = provider;
|
||||||
|
this.masterInput = masterInput;
|
||||||
|
if (attributes != null) {
|
||||||
|
setAttributes(attributes);
|
||||||
|
}
|
||||||
|
if (size != null) {
|
||||||
|
setSize(size);
|
||||||
|
}
|
||||||
|
if (!paused) {
|
||||||
|
resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doClose() throws IOException {
|
||||||
|
if (closed.compareAndSet(false, true)) {
|
||||||
|
pause();
|
||||||
|
super.doClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canPauseResume() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
synchronized (lock) {
|
||||||
|
paused = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause(boolean wait) throws InterruptedException {
|
||||||
|
Thread p;
|
||||||
|
synchronized (lock) {
|
||||||
|
paused = true;
|
||||||
|
p = pumpThread;
|
||||||
|
}
|
||||||
|
if (p != null) {
|
||||||
|
p.interrupt();
|
||||||
|
p.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resume() {
|
||||||
|
synchronized (lock) {
|
||||||
|
paused = false;
|
||||||
|
if (pumpThread == null) {
|
||||||
|
pumpThread = new Thread(this::pump, this + " input pump thread");
|
||||||
|
pumpThread.setDaemon(true);
|
||||||
|
pumpThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean paused() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pump() {
|
||||||
|
try {
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
while (true) {
|
||||||
|
int c = masterInput.read(buf);
|
||||||
|
if (c >= 0) {
|
||||||
|
processInputBytes(buf, 0, c);
|
||||||
|
}
|
||||||
|
if (c < 0 || closed.get()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
synchronized (lock) {
|
||||||
|
if (paused) {
|
||||||
|
pumpThread = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
processIOException(e);
|
||||||
|
} finally {
|
||||||
|
synchronized (lock) {
|
||||||
|
pumpThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
slaveInput.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor getCursorPosition(IntConsumer discarded) {
|
||||||
|
return CursorSupport.getCursorPosition(this, discarded);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalProvider getProvider() {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,371 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Attributes.ControlChar;
|
||||||
|
import org.jline.terminal.Attributes.InputFlag;
|
||||||
|
import org.jline.terminal.Attributes.LocalFlag;
|
||||||
|
import org.jline.terminal.Attributes.OutputFlag;
|
||||||
|
import org.jline.terminal.Size;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
import org.jline.utils.NonBlocking;
|
||||||
|
import org.jline.utils.NonBlockingPumpInputStream;
|
||||||
|
import org.jline.utils.NonBlockingReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract terminal with support for line discipline.
|
||||||
|
* The {@link Terminal} interface represents the slave
|
||||||
|
* side of a PTY, but implementations derived from this class
|
||||||
|
* will handle both the slave and master side of things.
|
||||||
|
* <p>
|
||||||
|
* In order to correctly handle line discipline, the terminal
|
||||||
|
* needs to read the input in advance in order to raise the
|
||||||
|
* signals as fast as possible.
|
||||||
|
* For example, when the user hits Ctrl+C, we can't wait until
|
||||||
|
* the application consumes all the read events.
|
||||||
|
* The same applies to echoing, when enabled, as the echoing
|
||||||
|
* has to happen as soon as the user hit the keyboard, and not
|
||||||
|
* only when the application running in the terminal processes
|
||||||
|
* the input.
|
||||||
|
*/
|
||||||
|
public class LineDisciplineTerminal extends AbstractTerminal {
|
||||||
|
|
||||||
|
private static final int PIPE_SIZE = 1024;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Master output stream
|
||||||
|
*/
|
||||||
|
protected final OutputStream masterOutput;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Slave input pipe write side
|
||||||
|
*/
|
||||||
|
protected final OutputStream slaveInputPipe;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Slave streams
|
||||||
|
*/
|
||||||
|
protected final NonBlockingPumpInputStream slaveInput;
|
||||||
|
protected final NonBlockingReader slaveReader;
|
||||||
|
protected final PrintWriter slaveWriter;
|
||||||
|
protected final OutputStream slaveOutput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console data
|
||||||
|
*/
|
||||||
|
protected final Attributes attributes;
|
||||||
|
|
||||||
|
protected final Size size;
|
||||||
|
|
||||||
|
protected boolean skipNextLf;
|
||||||
|
|
||||||
|
public LineDisciplineTerminal(String name, String type, OutputStream masterOutput, Charset encoding)
|
||||||
|
throws IOException {
|
||||||
|
this(name, type, masterOutput, encoding, SignalHandler.SIG_DFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public LineDisciplineTerminal(
|
||||||
|
String name, String type, OutputStream masterOutput, Charset encoding, SignalHandler signalHandler)
|
||||||
|
throws IOException {
|
||||||
|
super(name, type, encoding, signalHandler);
|
||||||
|
NonBlockingPumpInputStream input = NonBlocking.nonBlockingPumpInputStream(PIPE_SIZE);
|
||||||
|
this.slaveInputPipe = input.getOutputStream();
|
||||||
|
this.slaveInput = input;
|
||||||
|
this.slaveReader = NonBlocking.nonBlocking(getName(), slaveInput, encoding());
|
||||||
|
this.slaveOutput = new FilteringOutputStream();
|
||||||
|
this.slaveWriter = new PrintWriter(new OutputStreamWriter(slaveOutput, encoding()));
|
||||||
|
this.masterOutput = masterOutput;
|
||||||
|
this.attributes = getDefaultTerminalAttributes();
|
||||||
|
this.size = new Size(160, 50);
|
||||||
|
parseInfoCmp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Attributes getDefaultTerminalAttributes() {
|
||||||
|
// speed 9600 baud; 24 rows; 80 columns;
|
||||||
|
// lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl
|
||||||
|
// -echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo
|
||||||
|
// -extproc
|
||||||
|
// iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel iutf8
|
||||||
|
// -ignbrk brkint -inpck -ignpar -parmrk
|
||||||
|
// oflags: opost onlcr -oxtabs -onocr -onlret
|
||||||
|
// cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow
|
||||||
|
// -dtrflow -mdmbuf
|
||||||
|
// cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
|
||||||
|
// eol2 = <undef>; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;
|
||||||
|
// min = 1; quit = ^\\; reprint = ^R; start = ^Q; status = ^T;
|
||||||
|
// stop = ^S; susp = ^Z; time = 0; werase = ^W;
|
||||||
|
Attributes attr = new Attributes();
|
||||||
|
attr.setLocalFlags(EnumSet.of(
|
||||||
|
LocalFlag.ICANON,
|
||||||
|
LocalFlag.ISIG,
|
||||||
|
LocalFlag.IEXTEN,
|
||||||
|
LocalFlag.ECHO,
|
||||||
|
LocalFlag.ECHOE,
|
||||||
|
LocalFlag.ECHOKE,
|
||||||
|
LocalFlag.ECHOCTL,
|
||||||
|
LocalFlag.PENDIN));
|
||||||
|
attr.setInputFlags(EnumSet.of(
|
||||||
|
InputFlag.ICRNL,
|
||||||
|
InputFlag.IXON,
|
||||||
|
InputFlag.IXANY,
|
||||||
|
InputFlag.IMAXBEL,
|
||||||
|
InputFlag.IUTF8,
|
||||||
|
InputFlag.BRKINT));
|
||||||
|
attr.setOutputFlags(EnumSet.of(OutputFlag.OPOST, OutputFlag.ONLCR));
|
||||||
|
attr.setControlChar(ControlChar.VDISCARD, ctrl('O'));
|
||||||
|
attr.setControlChar(ControlChar.VDSUSP, ctrl('Y'));
|
||||||
|
attr.setControlChar(ControlChar.VEOF, ctrl('D'));
|
||||||
|
attr.setControlChar(ControlChar.VERASE, ctrl('?'));
|
||||||
|
attr.setControlChar(ControlChar.VINTR, ctrl('C'));
|
||||||
|
attr.setControlChar(ControlChar.VKILL, ctrl('U'));
|
||||||
|
attr.setControlChar(ControlChar.VLNEXT, ctrl('V'));
|
||||||
|
attr.setControlChar(ControlChar.VMIN, 1);
|
||||||
|
attr.setControlChar(ControlChar.VQUIT, ctrl('\\'));
|
||||||
|
attr.setControlChar(ControlChar.VREPRINT, ctrl('R'));
|
||||||
|
attr.setControlChar(ControlChar.VSTART, ctrl('Q'));
|
||||||
|
attr.setControlChar(ControlChar.VSTATUS, ctrl('T'));
|
||||||
|
attr.setControlChar(ControlChar.VSTOP, ctrl('S'));
|
||||||
|
attr.setControlChar(ControlChar.VSUSP, ctrl('Z'));
|
||||||
|
attr.setControlChar(ControlChar.VTIME, 0);
|
||||||
|
attr.setControlChar(ControlChar.VWERASE, ctrl('W'));
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ctrl(char c) {
|
||||||
|
return c == '?' ? 177 : c - 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonBlockingReader reader() {
|
||||||
|
return slaveReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintWriter writer() {
|
||||||
|
return slaveWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream input() {
|
||||||
|
return slaveInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream output() {
|
||||||
|
return slaveOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attributes getAttributes() {
|
||||||
|
return new Attributes(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Attributes attr) {
|
||||||
|
attributes.copy(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Size getSize() {
|
||||||
|
Size sz = new Size();
|
||||||
|
sz.copy(size);
|
||||||
|
return sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(Size sz) {
|
||||||
|
size.copy(sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void raise(Signal signal) {
|
||||||
|
Objects.requireNonNull(signal);
|
||||||
|
// Do not call clear() atm as this can cause
|
||||||
|
// deadlock between reading / writing threads
|
||||||
|
// TODO: any way to fix that ?
|
||||||
|
/*
|
||||||
|
if (!attributes.getLocalFlag(LocalFlag.NOFLSH)) {
|
||||||
|
try {
|
||||||
|
slaveReader.clear();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
echoSignal(signal);
|
||||||
|
super.raise(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master input processing.
|
||||||
|
* All data coming to the terminal should be provided
|
||||||
|
* using this method.
|
||||||
|
*
|
||||||
|
* @param c the input byte
|
||||||
|
* @throws IOException if anything wrong happens
|
||||||
|
*/
|
||||||
|
public void processInputByte(int c) throws IOException {
|
||||||
|
boolean flushOut = doProcessInputByte(c);
|
||||||
|
slaveInputPipe.flush();
|
||||||
|
if (flushOut) {
|
||||||
|
masterOutput.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processInputBytes(byte[] input) throws IOException {
|
||||||
|
processInputBytes(input, 0, input.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processInputBytes(byte[] input, int offset, int length) throws IOException {
|
||||||
|
boolean flushOut = false;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
flushOut |= doProcessInputByte(input[offset + i]);
|
||||||
|
}
|
||||||
|
slaveInputPipe.flush();
|
||||||
|
if (flushOut) {
|
||||||
|
masterOutput.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean doProcessInputByte(int c) throws IOException {
|
||||||
|
if (attributes.getLocalFlag(LocalFlag.ISIG)) {
|
||||||
|
if (c == attributes.getControlChar(ControlChar.VINTR)) {
|
||||||
|
raise(Signal.INT);
|
||||||
|
return false;
|
||||||
|
} else if (c == attributes.getControlChar(ControlChar.VQUIT)) {
|
||||||
|
raise(Signal.QUIT);
|
||||||
|
return false;
|
||||||
|
} else if (c == attributes.getControlChar(ControlChar.VSUSP)) {
|
||||||
|
raise(Signal.TSTP);
|
||||||
|
return false;
|
||||||
|
} else if (c == attributes.getControlChar(ControlChar.VSTATUS)) {
|
||||||
|
raise(Signal.INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attributes.getInputFlag(InputFlag.INORMEOL)) {
|
||||||
|
if (c == '\r') {
|
||||||
|
skipNextLf = true;
|
||||||
|
c = '\n';
|
||||||
|
} else if (c == '\n') {
|
||||||
|
if (skipNextLf) {
|
||||||
|
skipNextLf = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
skipNextLf = false;
|
||||||
|
}
|
||||||
|
} else if (c == '\r') {
|
||||||
|
if (attributes.getInputFlag(InputFlag.IGNCR)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (attributes.getInputFlag(InputFlag.ICRNL)) {
|
||||||
|
c = '\n';
|
||||||
|
}
|
||||||
|
} else if (c == '\n' && attributes.getInputFlag(InputFlag.INLCR)) {
|
||||||
|
c = '\r';
|
||||||
|
}
|
||||||
|
boolean flushOut = false;
|
||||||
|
if (attributes.getLocalFlag(LocalFlag.ECHO)) {
|
||||||
|
processOutputByte(c);
|
||||||
|
flushOut = true;
|
||||||
|
}
|
||||||
|
slaveInputPipe.write(c);
|
||||||
|
return flushOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master output processing.
|
||||||
|
* All data going to the master should be provided by this method.
|
||||||
|
*
|
||||||
|
* @param c the output byte
|
||||||
|
* @throws IOException if anything wrong happens
|
||||||
|
*/
|
||||||
|
protected void processOutputByte(int c) throws IOException {
|
||||||
|
if (attributes.getOutputFlag(OutputFlag.OPOST)) {
|
||||||
|
if (c == '\n') {
|
||||||
|
if (attributes.getOutputFlag(OutputFlag.ONLCR)) {
|
||||||
|
masterOutput.write('\r');
|
||||||
|
masterOutput.write('\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
masterOutput.write(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processIOException(IOException ioException) {
|
||||||
|
this.slaveInput.setIoException(ioException);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doClose() throws IOException {
|
||||||
|
super.doClose();
|
||||||
|
try {
|
||||||
|
slaveReader.close();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
slaveInputPipe.close();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
} finally {
|
||||||
|
slaveWriter.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalProvider getProvider() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SystemStream getSystemStream() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FilteringOutputStream extends OutputStream {
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
processOutputByte(b);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (b == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
} else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
} else if (len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
processOutputByte(b[off + i]);
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
masterOutput.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
masterOutput.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
net-cli/src/main/java/org/jline/terminal/impl/MouseSupport.java
Normal file
140
net-cli/src/main/java/org/jline/terminal/impl/MouseSupport.java
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOError;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
|
import org.jline.terminal.MouseEvent;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.utils.InfoCmp;
|
||||||
|
import org.jline.utils.InputStreamReader;
|
||||||
|
|
||||||
|
public class MouseSupport {
|
||||||
|
|
||||||
|
public static boolean hasMouseSupport(Terminal terminal) {
|
||||||
|
return terminal.getStringCapability(InfoCmp.Capability.key_mouse) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean trackMouse(Terminal terminal, Terminal.MouseTracking tracking) {
|
||||||
|
if (hasMouseSupport(terminal)) {
|
||||||
|
switch (tracking) {
|
||||||
|
case Off:
|
||||||
|
terminal.writer().write("\033[?1000l");
|
||||||
|
break;
|
||||||
|
case Normal:
|
||||||
|
terminal.writer().write("\033[?1005h\033[?1000h");
|
||||||
|
break;
|
||||||
|
case Button:
|
||||||
|
terminal.writer().write("\033[?1005h\033[?1002h");
|
||||||
|
break;
|
||||||
|
case Any:
|
||||||
|
terminal.writer().write("\033[?1005h\033[?1003h");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
terminal.flush();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MouseEvent readMouse(Terminal terminal, MouseEvent last) {
|
||||||
|
return readMouse(() -> readExt(terminal), last);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MouseEvent readMouse(IntSupplier reader, MouseEvent last) {
|
||||||
|
int cb = reader.getAsInt() - ' ';
|
||||||
|
int cx = reader.getAsInt() - ' ' - 1;
|
||||||
|
int cy = reader.getAsInt() - ' ' - 1;
|
||||||
|
MouseEvent.Type type;
|
||||||
|
MouseEvent.Button button;
|
||||||
|
EnumSet<MouseEvent.Modifier> modifiers = EnumSet.noneOf(MouseEvent.Modifier.class);
|
||||||
|
if ((cb & 4) == 4) {
|
||||||
|
modifiers.add(MouseEvent.Modifier.Shift);
|
||||||
|
}
|
||||||
|
if ((cb & 8) == 8) {
|
||||||
|
modifiers.add(MouseEvent.Modifier.Alt);
|
||||||
|
}
|
||||||
|
if ((cb & 16) == 16) {
|
||||||
|
modifiers.add(MouseEvent.Modifier.Control);
|
||||||
|
}
|
||||||
|
if ((cb & 64) == 64) {
|
||||||
|
type = MouseEvent.Type.Wheel;
|
||||||
|
button = (cb & 1) == 1 ? MouseEvent.Button.WheelDown : MouseEvent.Button.WheelUp;
|
||||||
|
} else {
|
||||||
|
int b = (cb & 3);
|
||||||
|
switch (b) {
|
||||||
|
case 0:
|
||||||
|
button = MouseEvent.Button.Button1;
|
||||||
|
if (last.getButton() == button
|
||||||
|
&& (last.getType() == MouseEvent.Type.Pressed
|
||||||
|
|| last.getType() == MouseEvent.Type.Dragged)) {
|
||||||
|
type = MouseEvent.Type.Dragged;
|
||||||
|
} else {
|
||||||
|
type = MouseEvent.Type.Pressed;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
button = MouseEvent.Button.Button2;
|
||||||
|
if (last.getButton() == button
|
||||||
|
&& (last.getType() == MouseEvent.Type.Pressed
|
||||||
|
|| last.getType() == MouseEvent.Type.Dragged)) {
|
||||||
|
type = MouseEvent.Type.Dragged;
|
||||||
|
} else {
|
||||||
|
type = MouseEvent.Type.Pressed;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
button = MouseEvent.Button.Button3;
|
||||||
|
if (last.getButton() == button
|
||||||
|
&& (last.getType() == MouseEvent.Type.Pressed
|
||||||
|
|| last.getType() == MouseEvent.Type.Dragged)) {
|
||||||
|
type = MouseEvent.Type.Dragged;
|
||||||
|
} else {
|
||||||
|
type = MouseEvent.Type.Pressed;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged) {
|
||||||
|
button = last.getButton();
|
||||||
|
type = MouseEvent.Type.Released;
|
||||||
|
} else {
|
||||||
|
button = MouseEvent.Button.NoButton;
|
||||||
|
type = MouseEvent.Type.Moved;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new MouseEvent(type, button, modifiers, cx, cy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readExt(Terminal terminal) {
|
||||||
|
try {
|
||||||
|
// The coordinates are encoded in UTF-8, so if that's not the input encoding,
|
||||||
|
// we need to get around
|
||||||
|
int c;
|
||||||
|
if (terminal.encoding() != StandardCharsets.UTF_8) {
|
||||||
|
c = new InputStreamReader(terminal.input(), StandardCharsets.UTF_8).read();
|
||||||
|
} else {
|
||||||
|
c = terminal.reader().read();
|
||||||
|
}
|
||||||
|
if (c < 0) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import org.jline.terminal.Terminal.Signal;
|
||||||
|
import org.jline.terminal.Terminal.SignalHandler;
|
||||||
|
|
||||||
|
public final class NativeSignalHandler implements SignalHandler {
|
||||||
|
|
||||||
|
public static final NativeSignalHandler SIG_DFL = new NativeSignalHandler();
|
||||||
|
|
||||||
|
public static final NativeSignalHandler SIG_IGN = new NativeSignalHandler();
|
||||||
|
|
||||||
|
private NativeSignalHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(Signal signal) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jline.terminal.spi.Pty;
|
||||||
|
import org.jline.utils.ClosedException;
|
||||||
|
import org.jline.utils.NonBlocking;
|
||||||
|
import org.jline.utils.NonBlockingInputStream;
|
||||||
|
import org.jline.utils.NonBlockingReader;
|
||||||
|
|
||||||
|
public class PosixPtyTerminal extends AbstractPosixTerminal {
|
||||||
|
|
||||||
|
private final InputStream in;
|
||||||
|
private final OutputStream out;
|
||||||
|
private final InputStream masterInput;
|
||||||
|
private final OutputStream masterOutput;
|
||||||
|
private final NonBlockingInputStream input;
|
||||||
|
private final OutputStream output;
|
||||||
|
private final NonBlockingReader reader;
|
||||||
|
private final PrintWriter writer;
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
private Thread inputPumpThread;
|
||||||
|
private Thread outputPumpThread;
|
||||||
|
private boolean paused = true;
|
||||||
|
|
||||||
|
public PosixPtyTerminal(String name, String type, Pty pty, InputStream in, OutputStream out, Charset encoding)
|
||||||
|
throws IOException {
|
||||||
|
this(name, type, pty, in, out, encoding, SignalHandler.SIG_DFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PosixPtyTerminal(
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
Pty pty,
|
||||||
|
InputStream in,
|
||||||
|
OutputStream out,
|
||||||
|
Charset encoding,
|
||||||
|
SignalHandler signalHandler)
|
||||||
|
throws IOException {
|
||||||
|
this(name, type, pty, in, out, encoding, signalHandler, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public PosixPtyTerminal(
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
Pty pty,
|
||||||
|
InputStream in,
|
||||||
|
OutputStream out,
|
||||||
|
Charset encoding,
|
||||||
|
SignalHandler signalHandler,
|
||||||
|
boolean paused)
|
||||||
|
throws IOException {
|
||||||
|
super(name, type, pty, encoding, signalHandler);
|
||||||
|
this.in = Objects.requireNonNull(in);
|
||||||
|
this.out = Objects.requireNonNull(out);
|
||||||
|
this.masterInput = pty.getMasterInput();
|
||||||
|
this.masterOutput = pty.getMasterOutput();
|
||||||
|
this.input = new InputStreamWrapper(NonBlocking.nonBlocking(name, pty.getSlaveInput()));
|
||||||
|
this.output = pty.getSlaveOutput();
|
||||||
|
this.reader = NonBlocking.nonBlocking(name, input, encoding());
|
||||||
|
this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
|
||||||
|
parseInfoCmp();
|
||||||
|
if (!paused) {
|
||||||
|
resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream input() {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonBlockingReader reader() {
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream output() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintWriter writer() {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doClose() throws IOException {
|
||||||
|
super.doClose();
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canPauseResume() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
synchronized (lock) {
|
||||||
|
paused = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause(boolean wait) throws InterruptedException {
|
||||||
|
Thread p1, p2;
|
||||||
|
synchronized (lock) {
|
||||||
|
paused = true;
|
||||||
|
p1 = inputPumpThread;
|
||||||
|
p2 = outputPumpThread;
|
||||||
|
}
|
||||||
|
if (p1 != null) {
|
||||||
|
p1.interrupt();
|
||||||
|
}
|
||||||
|
if (p2 != null) {
|
||||||
|
p2.interrupt();
|
||||||
|
}
|
||||||
|
if (p1 != null) {
|
||||||
|
p1.join();
|
||||||
|
}
|
||||||
|
if (p2 != null) {
|
||||||
|
p2.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resume() {
|
||||||
|
synchronized (lock) {
|
||||||
|
paused = false;
|
||||||
|
if (inputPumpThread == null) {
|
||||||
|
inputPumpThread = new Thread(this::pumpIn, this + " input pump thread");
|
||||||
|
inputPumpThread.setDaemon(true);
|
||||||
|
inputPumpThread.start();
|
||||||
|
}
|
||||||
|
if (outputPumpThread == null) {
|
||||||
|
outputPumpThread = new Thread(this::pumpOut, this + " output pump thread");
|
||||||
|
outputPumpThread.setDaemon(true);
|
||||||
|
outputPumpThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean paused() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pumpIn() {
|
||||||
|
try {
|
||||||
|
for (; ; ) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (paused) {
|
||||||
|
inputPumpThread = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int b = in.read();
|
||||||
|
if (b < 0) {
|
||||||
|
input.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
masterOutput.write(b);
|
||||||
|
masterOutput.flush();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
synchronized (lock) {
|
||||||
|
inputPumpThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pumpOut() {
|
||||||
|
try {
|
||||||
|
for (; ; ) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (paused) {
|
||||||
|
outputPumpThread = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int b = masterInput.read();
|
||||||
|
if (b < 0) {
|
||||||
|
input.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out.write(b);
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
synchronized (lock) {
|
||||||
|
outputPumpThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
close();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InputStreamWrapper extends NonBlockingInputStream {
|
||||||
|
|
||||||
|
private final NonBlockingInputStream in;
|
||||||
|
private volatile boolean closed;
|
||||||
|
|
||||||
|
protected InputStreamWrapper(NonBlockingInputStream in) {
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(long timeout, boolean isPeek) throws IOException {
|
||||||
|
if (closed) {
|
||||||
|
throw new ClosedException();
|
||||||
|
}
|
||||||
|
return in.read(timeout, isPeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2018, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jline.terminal.spi.Pty;
|
||||||
|
import org.jline.utils.FastBufferedOutputStream;
|
||||||
|
import org.jline.utils.NonBlocking;
|
||||||
|
import org.jline.utils.NonBlockingInputStream;
|
||||||
|
import org.jline.utils.NonBlockingReader;
|
||||||
|
import org.jline.utils.ShutdownHooks;
|
||||||
|
import org.jline.utils.ShutdownHooks.Task;
|
||||||
|
import org.jline.utils.Signals;
|
||||||
|
|
||||||
|
public class PosixSysTerminal extends AbstractPosixTerminal {
|
||||||
|
|
||||||
|
protected final NonBlockingInputStream input;
|
||||||
|
protected final OutputStream output;
|
||||||
|
protected final NonBlockingReader reader;
|
||||||
|
protected final PrintWriter writer;
|
||||||
|
protected final Map<Signal, Object> nativeHandlers = new HashMap<>();
|
||||||
|
protected final Task closer;
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public PosixSysTerminal(
|
||||||
|
String name, String type, Pty pty, Charset encoding, boolean nativeSignals, SignalHandler signalHandler)
|
||||||
|
throws IOException {
|
||||||
|
super(name, type, pty, encoding, signalHandler);
|
||||||
|
this.input = NonBlocking.nonBlocking(getName(), pty.getSlaveInput());
|
||||||
|
this.output = new FastBufferedOutputStream(pty.getSlaveOutput());
|
||||||
|
this.reader = NonBlocking.nonBlocking(getName(), input, encoding());
|
||||||
|
this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
|
||||||
|
parseInfoCmp();
|
||||||
|
if (nativeSignals) {
|
||||||
|
for (final Signal signal : Signal.values()) {
|
||||||
|
if (signalHandler == SignalHandler.SIG_DFL) {
|
||||||
|
nativeHandlers.put(signal, Signals.registerDefault(signal.name()));
|
||||||
|
} else {
|
||||||
|
nativeHandlers.put(signal, Signals.register(signal.name(), () -> raise(signal)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closer = PosixSysTerminal.this::close;
|
||||||
|
ShutdownHooks.add(closer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SignalHandler handle(Signal signal, SignalHandler handler) {
|
||||||
|
SignalHandler prev = super.handle(signal, handler);
|
||||||
|
if (prev != handler) {
|
||||||
|
if (handler == SignalHandler.SIG_DFL) {
|
||||||
|
Signals.registerDefault(signal.name());
|
||||||
|
} else {
|
||||||
|
Signals.register(signal.name(), () -> raise(signal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonBlockingReader reader() {
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintWriter writer() {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream input() {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream output() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doClose() throws IOException {
|
||||||
|
ShutdownHooks.remove(closer);
|
||||||
|
for (Map.Entry<Signal, Object> entry : nativeHandlers.entrySet()) {
|
||||||
|
Signals.unregister(entry.getKey().name(), entry.getValue());
|
||||||
|
}
|
||||||
|
super.doClose();
|
||||||
|
// Do not call reader.close()
|
||||||
|
reader.shutdown();
|
||||||
|
}
|
||||||
|
}
|
310
net-cli/src/main/java/org/jline/terminal/impl/exec/ExecPty.java
Normal file
310
net-cli/src/main/java/org/jline/terminal/impl/exec/ExecPty.java
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2016, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl.exec;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Attributes.ControlChar;
|
||||||
|
import org.jline.terminal.Attributes.ControlFlag;
|
||||||
|
import org.jline.terminal.Attributes.InputFlag;
|
||||||
|
import org.jline.terminal.Attributes.LocalFlag;
|
||||||
|
import org.jline.terminal.Attributes.OutputFlag;
|
||||||
|
import org.jline.terminal.Size;
|
||||||
|
import org.jline.terminal.impl.AbstractPty;
|
||||||
|
import org.jline.terminal.spi.Pty;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
import org.jline.utils.OSUtils;
|
||||||
|
import static org.jline.utils.ExecHelper.exec;
|
||||||
|
|
||||||
|
public class ExecPty extends AbstractPty implements Pty {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
protected ExecPty(TerminalProvider provider, SystemStream systemStream, String name) {
|
||||||
|
super(provider, systemStream);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Pty current(TerminalProvider provider, SystemStream systemStream) throws IOException {
|
||||||
|
try {
|
||||||
|
String result = exec(true, OSUtils.TTY_COMMAND);
|
||||||
|
if (systemStream != SystemStream.Output && systemStream != SystemStream.Error) {
|
||||||
|
throw new IllegalArgumentException("systemStream should be Output or Error: " + systemStream);
|
||||||
|
}
|
||||||
|
return new ExecPty(provider, systemStream, result.trim());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException("Not a tty", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Attributes doGetAttr(String cfg) throws IOException {
|
||||||
|
Attributes attributes = new Attributes();
|
||||||
|
for (InputFlag flag : InputFlag.values()) {
|
||||||
|
Boolean value = doGetFlag(cfg, flag);
|
||||||
|
if (value != null) {
|
||||||
|
attributes.setInputFlag(flag, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (OutputFlag flag : OutputFlag.values()) {
|
||||||
|
Boolean value = doGetFlag(cfg, flag);
|
||||||
|
if (value != null) {
|
||||||
|
attributes.setOutputFlag(flag, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ControlFlag flag : ControlFlag.values()) {
|
||||||
|
Boolean value = doGetFlag(cfg, flag);
|
||||||
|
if (value != null) {
|
||||||
|
attributes.setControlFlag(flag, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (LocalFlag flag : LocalFlag.values()) {
|
||||||
|
Boolean value = doGetFlag(cfg, flag);
|
||||||
|
if (value != null) {
|
||||||
|
attributes.setLocalFlag(flag, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ControlChar cchar : ControlChar.values()) {
|
||||||
|
String name = cchar.name().toLowerCase().substring(1);
|
||||||
|
if ("reprint".endsWith(name)) {
|
||||||
|
name = "(?:reprint|rprnt)";
|
||||||
|
}
|
||||||
|
Matcher matcher =
|
||||||
|
Pattern.compile("[\\s;]" + name + "\\s*=\\s*(.+?)[\\s;]").matcher(cfg);
|
||||||
|
if (matcher.find()) {
|
||||||
|
attributes.setControlChar(
|
||||||
|
cchar, parseControlChar(matcher.group(1).toUpperCase()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Boolean doGetFlag(String cfg, Enum<?> flag) {
|
||||||
|
Matcher matcher = Pattern.compile("(?:^|[\\s;])(\\-?" + flag.name().toLowerCase() + ")(?:[\\s;]|$)")
|
||||||
|
.matcher(cfg);
|
||||||
|
return matcher.find() ? !matcher.group(1).startsWith("-") : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parseControlChar(String str) {
|
||||||
|
// undef
|
||||||
|
if ("<UNDEF>".equals(str)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// del
|
||||||
|
if ("DEL".equalsIgnoreCase(str)) {
|
||||||
|
return 127;
|
||||||
|
}
|
||||||
|
// octal
|
||||||
|
if (str.charAt(0) == '0') {
|
||||||
|
return Integer.parseInt(str, 8);
|
||||||
|
}
|
||||||
|
// decimal
|
||||||
|
if (str.charAt(0) >= '1' && str.charAt(0) <= '9') {
|
||||||
|
return Integer.parseInt(str, 10);
|
||||||
|
}
|
||||||
|
// control char
|
||||||
|
if (str.charAt(0) == '^') {
|
||||||
|
if (str.charAt(1) == '?') {
|
||||||
|
return 127;
|
||||||
|
} else {
|
||||||
|
return str.charAt(1) - 64;
|
||||||
|
}
|
||||||
|
} else if (str.charAt(0) == 'M' && str.charAt(1) == '-') {
|
||||||
|
if (str.charAt(2) == '^') {
|
||||||
|
if (str.charAt(3) == '?') {
|
||||||
|
return 127 + 128;
|
||||||
|
} else {
|
||||||
|
return str.charAt(3) - 64 + 128;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return str.charAt(2) + 128;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return str.charAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Size doGetSize(String cfg) throws IOException {
|
||||||
|
return new Size(doGetInt("columns", cfg), doGetInt("rows", cfg));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int doGetInt(String name, String cfg) throws IOException {
|
||||||
|
String[] patterns = new String[]{
|
||||||
|
"\\b([0-9]+)\\s+" + name + "\\b", "\\b" + name + "\\s+([0-9]+)\\b", "\\b" + name + "\\s*=\\s*([0-9]+)\\b"
|
||||||
|
};
|
||||||
|
for (String pattern : patterns) {
|
||||||
|
Matcher matcher = Pattern.compile(pattern).matcher(cfg);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return Integer.parseInt(matcher.group(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getMasterInput() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getMasterOutput() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream doGetSlaveInput() throws IOException {
|
||||||
|
return systemStream != null ? new FileInputStream(FileDescriptor.in) : new FileInputStream(getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getSlaveOutput() throws IOException {
|
||||||
|
return systemStream == SystemStream.Output
|
||||||
|
? new FileOutputStream(FileDescriptor.out)
|
||||||
|
: systemStream == SystemStream.Error
|
||||||
|
? new FileOutputStream(FileDescriptor.err)
|
||||||
|
: new FileOutputStream(getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Attributes getAttr() throws IOException {
|
||||||
|
String cfg = doGetConfig();
|
||||||
|
return doGetAttr(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doSetAttr(Attributes attr) throws IOException {
|
||||||
|
List<String> commands = getFlagsToSet(attr, getAttr());
|
||||||
|
if (!commands.isEmpty()) {
|
||||||
|
commands.add(0, OSUtils.STTY_COMMAND);
|
||||||
|
if (systemStream == null) {
|
||||||
|
commands.add(1, OSUtils.STTY_F_OPTION);
|
||||||
|
commands.add(2, getName());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
exec(systemStream != null, commands.toArray(new String[0]));
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Handle partial failures with GNU stty, see #97
|
||||||
|
if (e.toString().contains("unable to perform all requested operations")) {
|
||||||
|
commands = getFlagsToSet(attr, getAttr());
|
||||||
|
if (!commands.isEmpty()) {
|
||||||
|
throw new IOException("Could not set the following flags: " + String.join(", ", commands), e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<String> getFlagsToSet(Attributes attr, Attributes current) {
|
||||||
|
List<String> commands = new ArrayList<>();
|
||||||
|
for (InputFlag flag : InputFlag.values()) {
|
||||||
|
if (attr.getInputFlag(flag) != current.getInputFlag(flag) && flag != InputFlag.INORMEOL) {
|
||||||
|
commands.add((attr.getInputFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (OutputFlag flag : OutputFlag.values()) {
|
||||||
|
if (attr.getOutputFlag(flag) != current.getOutputFlag(flag)) {
|
||||||
|
commands.add((attr.getOutputFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ControlFlag flag : ControlFlag.values()) {
|
||||||
|
if (attr.getControlFlag(flag) != current.getControlFlag(flag)) {
|
||||||
|
commands.add((attr.getControlFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (LocalFlag flag : LocalFlag.values()) {
|
||||||
|
if (attr.getLocalFlag(flag) != current.getLocalFlag(flag)) {
|
||||||
|
commands.add((attr.getLocalFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String undef = System.getProperty("os.name").toLowerCase().startsWith("hp") ? "^-" : "undef";
|
||||||
|
for (ControlChar cchar : ControlChar.values()) {
|
||||||
|
int v = attr.getControlChar(cchar);
|
||||||
|
if (v >= 0 && v != current.getControlChar(cchar)) {
|
||||||
|
String str = "";
|
||||||
|
commands.add(cchar.name().toLowerCase().substring(1));
|
||||||
|
if (cchar == ControlChar.VMIN || cchar == ControlChar.VTIME) {
|
||||||
|
commands.add(Integer.toString(v));
|
||||||
|
} else if (v == 0) {
|
||||||
|
commands.add(undef);
|
||||||
|
} else {
|
||||||
|
if (v >= 128) {
|
||||||
|
v -= 128;
|
||||||
|
str += "M-";
|
||||||
|
}
|
||||||
|
if (v < 32 || v == 127) {
|
||||||
|
v ^= 0x40;
|
||||||
|
str += "^";
|
||||||
|
}
|
||||||
|
str += (char) v;
|
||||||
|
commands.add(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Size getSize() throws IOException {
|
||||||
|
String cfg = doGetConfig();
|
||||||
|
return doGetSize(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSize(Size size) throws IOException {
|
||||||
|
if (systemStream != null) {
|
||||||
|
exec(
|
||||||
|
true,
|
||||||
|
OSUtils.STTY_COMMAND,
|
||||||
|
"columns",
|
||||||
|
Integer.toString(size.getColumns()),
|
||||||
|
"rows",
|
||||||
|
Integer.toString(size.getRows()));
|
||||||
|
} else {
|
||||||
|
exec(
|
||||||
|
false,
|
||||||
|
OSUtils.STTY_COMMAND,
|
||||||
|
OSUtils.STTY_F_OPTION,
|
||||||
|
getName(),
|
||||||
|
"columns",
|
||||||
|
Integer.toString(size.getColumns()),
|
||||||
|
"rows",
|
||||||
|
Integer.toString(size.getRows()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String doGetConfig() throws IOException {
|
||||||
|
return systemStream != null
|
||||||
|
? exec(true, OSUtils.STTY_COMMAND, "-a")
|
||||||
|
: exec(false, OSUtils.STTY_COMMAND, OSUtils.STTY_F_OPTION, getName(), "-a");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ExecPty[" + getName() + (systemStream != null ? ", system]" : "]");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022, the original author(s).
|
||||||
|
*
|
||||||
|
* This software is distributable under the BSD license. See the terms of the
|
||||||
|
* BSD license in the documentation provided with this software.
|
||||||
|
*
|
||||||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.jline.terminal.impl.exec;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import org.jline.terminal.Attributes;
|
||||||
|
import org.jline.terminal.Size;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.terminal.TerminalBuilder;
|
||||||
|
import org.jline.terminal.impl.ExternalTerminal;
|
||||||
|
import org.jline.terminal.impl.PosixSysTerminal;
|
||||||
|
import org.jline.terminal.spi.Pty;
|
||||||
|
import org.jline.terminal.spi.SystemStream;
|
||||||
|
import org.jline.terminal.spi.TerminalProvider;
|
||||||
|
import org.jline.utils.ExecHelper;
|
||||||
|
import org.jline.utils.Log;
|
||||||
|
import org.jline.utils.OSUtils;
|
||||||
|
import static org.jline.terminal.TerminalBuilder.PROP_REDIRECT_PIPE_CREATION_MODE;
|
||||||
|
import static org.jline.terminal.TerminalBuilder.PROP_REDIRECT_PIPE_CREATION_MODE_DEFAULT;
|
||||||
|
import static org.jline.terminal.TerminalBuilder.PROP_REDIRECT_PIPE_CREATION_MODE_REFLECTION;
|
||||||
|
|
||||||
|
public class ExecTerminalProvider implements TerminalProvider {
|
||||||
|
|
||||||
|
private static boolean warned;
|
||||||
|
private static RedirectPipeCreator redirectPipeCreator;
|
||||||
|
|
||||||
|
protected static ProcessBuilder.Redirect newDescriptor(FileDescriptor fd) {
|
||||||
|
if (redirectPipeCreator == null) {
|
||||||
|
String str = System.getProperty(PROP_REDIRECT_PIPE_CREATION_MODE, PROP_REDIRECT_PIPE_CREATION_MODE_DEFAULT);
|
||||||
|
String[] modes = str.split(",");
|
||||||
|
IllegalStateException ise = new IllegalStateException("Unable to create RedirectPipe");
|
||||||
|
for (String mode : modes) {
|
||||||
|
try {
|
||||||
|
switch (mode) {
|
||||||
|
case PROP_REDIRECT_PIPE_CREATION_MODE_REFLECTION:
|
||||||
|
redirectPipeCreator = new ReflectionRedirectPipeCreator();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// ignore
|
||||||
|
ise.addSuppressed(t);
|
||||||
|
}
|
||||||
|
if (redirectPipeCreator != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (redirectPipeCreator == null) {
|
||||||
|
throw ise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redirectPipeCreator.newRedirectPipe(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
return TerminalBuilder.PROP_PROVIDER_EXEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pty current(SystemStream systemStream) throws IOException {
|
||||||
|
return ExecPty.current(this, systemStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Terminal sysTerminal(
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
boolean ansiPassThrough,
|
||||||
|
Charset encoding,
|
||||||
|
boolean nativeSignals,
|
||||||
|
Terminal.SignalHandler signalHandler,
|
||||||
|
boolean paused,
|
||||||
|
SystemStream systemStream)
|
||||||
|
throws IOException {
|
||||||
|
if (OSUtils.IS_WINDOWS) {
|
||||||
|
return winSysTerminal(
|
||||||
|
name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream);
|
||||||
|
} else {
|
||||||
|
return posixSysTerminal(
|
||||||
|
name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Terminal winSysTerminal(
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
boolean ansiPassThrough,
|
||||||
|
Charset encoding,
|
||||||
|
boolean nativeSignals,
|
||||||
|
Terminal.SignalHandler signalHandler,
|
||||||
|
boolean paused,
|
||||||
|
SystemStream systemStream)
|
||||||
|
throws IOException {
|
||||||
|
if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) {
|
||||||
|
Pty pty = current(systemStream);
|
||||||
|
return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Terminal posixSysTerminal(
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
boolean ansiPassThrough,
|
||||||
|
Charset encoding,
|
||||||
|
boolean nativeSignals,
|
||||||
|
Terminal.SignalHandler signalHandler,
|
||||||
|
boolean paused,
|
||||||
|
SystemStream systemStream)
|
||||||
|
throws IOException {
|
||||||
|
Pty pty = current(systemStream);
|
||||||
|
return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Terminal newTerminal(
|
||||||
|
String name,
|
||||||
|
String type,
|
||||||
|
InputStream in,
|
||||||
|
OutputStream out,
|
||||||
|
Charset encoding,
|
||||||
|
Terminal.SignalHandler signalHandler,
|
||||||
|
boolean paused,
|
||||||
|
Attributes attributes,
|
||||||
|
Size size)
|
||||||
|
throws IOException {
|
||||||
|
return new ExternalTerminal(this, name, type, in, out, encoding, signalHandler, paused, attributes, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSystemStream(SystemStream stream) {
|
||||||
|
try {
|
||||||
|
return isPosixSystemStream(stream) || isWindowsSystemStream(stream);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWindowsSystemStream(SystemStream stream) {
|
||||||
|
return systemStreamName(stream) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPosixSystemStream(SystemStream stream) {
|
||||||
|
try {
|
||||||
|
Process p = new ProcessBuilder(OSUtils.TEST_COMMAND, "-t", Integer.toString(stream.ordinal()))
|
||||||
|
.inheritIO()
|
||||||
|
.start();
|
||||||
|
return p.waitFor() == 0;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.debug("ExecTerminalProvider failed 'test -t' for " + stream, t);
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String systemStreamName(SystemStream stream) {
|
||||||
|
try {
|
||||||
|
ProcessBuilder.Redirect input = stream == SystemStream.Input
|
||||||
|
? ProcessBuilder.Redirect.INHERIT
|
||||||
|
: newDescriptor(stream == SystemStream.Output ? FileDescriptor.out : FileDescriptor.err);
|
||||||
|
Process p =
|
||||||
|
new ProcessBuilder(OSUtils.TTY_COMMAND).redirectInput(input).start();
|
||||||
|
String result = ExecHelper.waitAndCapture(p);
|
||||||
|
if (p.exitValue() == 0) {
|
||||||
|
return result.trim();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if ("java.lang.reflect.InaccessibleObjectException"
|
||||||
|
.equals(t.getClass().getName())
|
||||||
|
&& !warned) {
|
||||||
|
Log.warn(
|
||||||
|
"The ExecTerminalProvider requires the JVM options: '--add-opens java.base/java.lang=ALL-UNNAMED'");
|
||||||
|
warned = true;
|
||||||
|
}
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int systemStreamWidth(SystemStream stream) {
|
||||||
|
try (ExecPty pty = new ExecPty(this, stream, null)) {
|
||||||
|
return pty.getSize().getColumns();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TerminalProvider[" + name() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RedirectPipeCreator {
|
||||||
|
ProcessBuilder.Redirect newRedirectPipe(FileDescriptor fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflection based file descriptor creator.
|
||||||
|
* This requires the following option
|
||||||
|
* --add-opens java.base/java.lang=ALL-UNNAMED
|
||||||
|
*/
|
||||||
|
static class ReflectionRedirectPipeCreator implements RedirectPipeCreator {
|
||||||
|
private final Constructor<ProcessBuilder.Redirect> constructor;
|
||||||
|
private final Field fdField;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ReflectionRedirectPipeCreator() throws Exception {
|
||||||
|
Class<?> rpi = Class.forName("java.lang.ProcessBuilder$RedirectPipeImpl");
|
||||||
|
constructor = (Constructor<ProcessBuilder.Redirect>) rpi.getDeclaredConstructor();
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
fdField = rpi.getDeclaredField("fd");
|
||||||
|
fdField.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcessBuilder.Redirect newRedirectPipe(FileDescriptor fd) {
|
||||||
|
try {
|
||||||
|
ProcessBuilder.Redirect input = constructor.newInstance();
|
||||||
|
fdField.set(input, fd);
|
||||||
|
return input;
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
// This should not happen as the field has been set accessible
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue