/*
* Copyright (c) 2009-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.planet57.gshell.shell;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import org.jline.reader.Candidate;
import org.jline.reader.Completer;
import org.jline.reader.LineReader;
import org.jline.reader.ParsedLine;
import org.jline.reader.impl.DefaultParser;
import org.jline.reader.impl.completer.AggregateCompleter;
import org.sonatype.goodies.common.ComponentSupport;
import com.google.common.collect.Lists;
import com.planet57.gshell.command.CommandAction;
import com.planet57.gshell.command.resolver.CommandResolver;
import com.planet57.gshell.command.resolver.Node;
/**
* Shell {@link Completer}.
*
* @since 3.0
*/
@Named("shell")
public class ShellCompleter
extends ComponentSupport
implements Completer
{
private final CommandResolver commandResolver;
private final Completer commandCompleter;
@Inject
public ShellCompleter(final CommandResolver commandResolver,
@Named("alias-name") final Completer aliasNameCompleter,
@Named("node-path") final Completer nodePathCompleter)
{
this.commandResolver = checkNotNull(commandResolver);
this.commandCompleter = new AggregateCompleter(aliasNameCompleter, nodePathCompleter);
}
@Override
public void complete(final LineReader reader, final ParsedLine line, final List<Candidate> candidates) {
explain("Parsed-line", line);
if (line.wordIndex() == 0) {
commandCompleter.complete(reader, line, candidates);
}
else {
String command = line.words().get(0);
log.trace("Command: {}", command);
// resolve node for command, this should be non-null?
Node node = commandResolver.resolve(command);
log.trace("Node: {}", node);
if (node != null) {
CommandAction action = node.getAction();
if (action instanceof CommandAction.Completable) {
Completer completer = ((CommandAction.Completable)action).getCompleter();
log.trace("Completer: {}", completer);
// HACK: complexity here to re-use ArgumentCompleter; not terribly efficient
ParsedLine arguments = extractCommandArguments(line);
explain("Command-arguments", arguments);
completer.complete(reader, arguments, candidates);
}
}
}
}
/**
* Helper to log {@link ParsedLine} details.
*/
private void explain(final String message, final ParsedLine line) {
// ParsedLine has no sane toString(); render all its details to logging
log.trace("{}: line={}, words={}, word-index: {}, word-cursor: {}, cursor: {}",
message,
line.line(),
line.words(),
line.wordIndex(),
line.wordCursor(),
line.cursor()
);
}
/**
* Extract the command specific portions of the given line.
*
* This is everything past the first word.
*/
private static ParsedLine extractCommandArguments(final ParsedLine line) {
// copy the list, so we can mutate and pop the first item off
LinkedList<String> words = Lists.newLinkedList(line.words());
String remove = words.pop();
String rawLine = line.line();
// rebuild that list sans the first argument
if (remove.length() > rawLine.length()) {
return new DefaultParser.ArgumentList(
rawLine.substring(remove.length() + 1, rawLine.length()),
words,
line.wordIndex() - 1,
line.wordCursor(),
line.cursor() - remove.length() + 1
);
}
else {
return new DefaultParser.ArgumentList("", Collections.emptyList(), 0, 0, 0);
}
}
}