891 lines
21 KiB
C++
891 lines
21 KiB
C++
/*
|
|
* \brief Line editor for command-line interfaces
|
|
* \author Norman Feske
|
|
* \date 2013-03-18
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2013-2017 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
#ifndef _LINE_EDITOR_H_
|
|
#define _LINE_EDITOR_H_
|
|
|
|
/* Genode includes */
|
|
#include <terminal_session/connection.h>
|
|
#include <util/string.h>
|
|
#include <util/list.h>
|
|
#include <util/token.h>
|
|
#include <util/misc_math.h>
|
|
#include <base/snprintf.h>
|
|
|
|
namespace Cli_monitor {
|
|
|
|
using Genode::List;
|
|
using Genode::max;
|
|
using Genode::strlen;
|
|
using Genode::strncpy;
|
|
using Genode::snprintf;
|
|
using Genode::strcmp;
|
|
using Genode::size_t;
|
|
using Genode::off_t;
|
|
using Genode::Interface;
|
|
|
|
struct Completable;
|
|
struct Argument;
|
|
struct Parameter;
|
|
struct Command_line;
|
|
struct Command;
|
|
struct Command_registry;
|
|
struct Scanner_policy;
|
|
struct Argument_tracker;
|
|
struct Line_editor;
|
|
|
|
typedef Genode::Token<Scanner_policy> Token;
|
|
}
|
|
|
|
|
|
struct Cli_monitor::Completable
|
|
{
|
|
typedef Genode::String<64> Name;
|
|
typedef Genode::String<160> Short_help;
|
|
|
|
Name const _name;
|
|
Short_help const _short_help;
|
|
|
|
Name name() const { return _name; }
|
|
Short_help short_help() const { return _short_help; }
|
|
|
|
Completable(char const *name, char const *short_help)
|
|
: _name(name), _short_help(short_help) { }
|
|
};
|
|
|
|
|
|
/**
|
|
* Representation of normal command-line argument
|
|
*/
|
|
struct Cli_monitor::Argument : Completable
|
|
{
|
|
Argument(char const *name, char const *short_help)
|
|
: Completable(name, short_help) { }
|
|
|
|
char const *name_suffix() const { return ""; }
|
|
};
|
|
|
|
|
|
/**
|
|
* Representation of a parameter of the form '--tag value'
|
|
*/
|
|
struct Cli_monitor::Parameter : List<Parameter>::Element, Completable
|
|
{
|
|
enum Type { IDENT, NUMBER, VOID };
|
|
|
|
Type const _type;
|
|
|
|
Parameter(char const *name, Type type, char const *short_help)
|
|
:
|
|
Completable(name, short_help), _type(type)
|
|
{ }
|
|
|
|
bool needs_value() const { return _type != VOID; }
|
|
|
|
char const *name_suffix() const
|
|
{
|
|
switch (_type) {
|
|
case IDENT: return "<identifier>";
|
|
case NUMBER: return "<number>";
|
|
case VOID: return "";
|
|
}
|
|
return "";
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Representation of a command that can have arguments and parameters
|
|
*/
|
|
struct Cli_monitor::Command : private List<Command>::Element,
|
|
private Completable
|
|
{
|
|
List<Parameter> _parameters { };
|
|
|
|
friend class List<Command>;
|
|
|
|
using List<Command>::Element::next;
|
|
using Completable::name;
|
|
using Completable::short_help;
|
|
|
|
/**
|
|
* Functor that takes a command 'Argument' object as argument
|
|
*/
|
|
struct Argument_fn : Interface
|
|
{
|
|
virtual void operator () (Argument const &) const = 0;
|
|
};
|
|
|
|
Command(char const *name, char const *short_help)
|
|
: Completable(name, short_help) { }
|
|
|
|
virtual ~Command() { }
|
|
|
|
void add_parameter(Parameter &par) { _parameters.insert(&par); }
|
|
|
|
char const *name_suffix() const { return ""; }
|
|
|
|
List<Parameter> ¶meters() { return _parameters; }
|
|
|
|
virtual void execute(Command_line &, Terminal::Session &terminal) = 0;
|
|
|
|
/**
|
|
* Command-specific support for 'for_each_argument'
|
|
*/
|
|
virtual void _for_each_argument(Argument_fn const &) const { };
|
|
|
|
/**
|
|
* Execute functor 'fn' for each command argument
|
|
*/
|
|
template <typename FN>
|
|
void for_each_argument(FN const &fn) const
|
|
{
|
|
struct _Fn : Argument_fn
|
|
{
|
|
FN const &fn;
|
|
void operator () (Argument const &arg) const override { fn(arg); }
|
|
_Fn(FN const &fn) : fn(fn) { }
|
|
} _fn(fn);
|
|
|
|
_for_each_argument(_fn);
|
|
}
|
|
};
|
|
|
|
|
|
struct Cli_monitor::Command_registry : List<Command> { };
|
|
|
|
|
|
/**
|
|
* Scanner policy that accepts '-', '.' and '_' as valid identifier characters
|
|
*/
|
|
struct Cli_monitor::Scanner_policy
|
|
{
|
|
static bool identifier_char(char c, unsigned i)
|
|
{
|
|
return Genode::is_letter(c) || (c == '_') || (c == '-') || (c == '.')
|
|
|| (i && Genode::is_digit(c));
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* State machine used for sequentially parsing command-line arguments
|
|
*/
|
|
struct Cli_monitor::Argument_tracker
|
|
{
|
|
private:
|
|
|
|
Command &_command;
|
|
|
|
enum State { EXPECT_COMMAND, EXPECT_SPACE_BEFORE_ARG, EXPECT_ARG,
|
|
EXPECT_SPACE_BEFORE_VAL, EXPECT_VAL, INVALID };
|
|
|
|
State _state;
|
|
|
|
/**
|
|
* Return true if there is exactly one complete match and no additional
|
|
* partial matches
|
|
*/
|
|
static bool _one_matching_argument(char const *str, size_t str_len,
|
|
Command const &command)
|
|
{
|
|
unsigned complete_cnt = 0, partial_cnt = 0;
|
|
|
|
auto argument_fn = [&] (Argument const &arg) {
|
|
|
|
if (strcmp(arg.name().string(), str, str_len) == 0) {
|
|
partial_cnt++;
|
|
|
|
if (strlen(arg.name().string()) == str_len)
|
|
complete_cnt++;
|
|
}
|
|
};
|
|
|
|
command.for_each_argument(argument_fn);
|
|
|
|
return partial_cnt == 1 && complete_cnt == 1;
|
|
}
|
|
|
|
public:
|
|
|
|
Argument_tracker(Command &command)
|
|
: _command(command), _state(EXPECT_COMMAND) { }
|
|
|
|
template <typename T>
|
|
static T *lookup(char const *str, size_t str_len,
|
|
List<T> &list)
|
|
{
|
|
Token tag(str, str_len);
|
|
for (T *curr = list.first(); curr; curr = curr->next())
|
|
if (strcmp(tag.start(), curr->name().string(), tag.len()) == 0
|
|
&& strlen(curr->name().string()) == tag.len())
|
|
return curr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
template <typename T>
|
|
static T *lookup(Token token, List<T> &list)
|
|
{
|
|
return lookup(token.start(), token.len(), list);
|
|
}
|
|
|
|
void supply_token(Token token, bool token_may_be_incomplete = false)
|
|
{
|
|
switch (_state) {
|
|
|
|
case INVALID: break;
|
|
|
|
case EXPECT_COMMAND:
|
|
|
|
if (token.type() == Token::IDENT) {
|
|
_state = EXPECT_SPACE_BEFORE_ARG;
|
|
break;
|
|
}
|
|
_state = INVALID;
|
|
break;
|
|
|
|
case EXPECT_SPACE_BEFORE_ARG:
|
|
|
|
if (token.type() == Token::WHITESPACE)
|
|
_state = EXPECT_ARG;
|
|
break;
|
|
|
|
case EXPECT_ARG:
|
|
|
|
if (token.type() == Token::IDENT) {
|
|
|
|
Parameter *parameter =
|
|
lookup(token.start(), token.len(), _command.parameters());
|
|
|
|
if (parameter && parameter->needs_value()) {
|
|
_state = EXPECT_SPACE_BEFORE_VAL;
|
|
break;
|
|
}
|
|
|
|
if (!token_may_be_incomplete
|
|
|| _one_matching_argument(token.start(), token.len(), _command))
|
|
_state = EXPECT_SPACE_BEFORE_ARG;
|
|
}
|
|
break;
|
|
|
|
case EXPECT_SPACE_BEFORE_VAL:
|
|
|
|
if (token.type() == Token::WHITESPACE)
|
|
_state = EXPECT_VAL;
|
|
break;
|
|
|
|
case EXPECT_VAL:
|
|
|
|
if (token.type() == Token::IDENT
|
|
|| token.type() == Token::NUMBER) {
|
|
|
|
_state = EXPECT_SPACE_BEFORE_ARG;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool valid() const { return _state != INVALID; }
|
|
bool expect_arg() const { return _state == EXPECT_ARG; }
|
|
bool expect_space() const { return _state == EXPECT_SPACE_BEFORE_ARG
|
|
|| _state == EXPECT_SPACE_BEFORE_VAL; }
|
|
};
|
|
|
|
|
|
/**
|
|
* Editing and completion logic
|
|
*/
|
|
class Cli_monitor::Line_editor
|
|
{
|
|
private:
|
|
|
|
char const *_prompt;
|
|
size_t const _prompt_len;
|
|
char * const _buf;
|
|
size_t const _buf_size;
|
|
unsigned _cursor_pos = 0;
|
|
Terminal::Session &_terminal;
|
|
Command_registry &_commands;
|
|
bool _complete = false;
|
|
|
|
/**
|
|
* State tracker for escape sequences within user input
|
|
*
|
|
* This tracker is used to decode special keys (e.g., cursor keys).
|
|
*/
|
|
struct Seq_tracker
|
|
{
|
|
enum State { INIT, GOT_ESC, GOT_FIRST } _state = INIT;
|
|
char _normal = 0, _first = 0, _second = 0;
|
|
bool _sequence_complete { false };
|
|
|
|
Seq_tracker() { }
|
|
|
|
void input(char c)
|
|
{
|
|
switch (_state) {
|
|
case INIT:
|
|
if (c == 27)
|
|
_state = GOT_ESC;
|
|
else
|
|
_normal = c;
|
|
_sequence_complete = false;
|
|
break;
|
|
|
|
case GOT_ESC:
|
|
_first = c;
|
|
_state = GOT_FIRST;
|
|
break;
|
|
|
|
case GOT_FIRST:
|
|
_second = c;
|
|
_state = INIT;
|
|
_sequence_complete = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool normal() const { return _state == INIT && !_sequence_complete; }
|
|
|
|
char normal_char() const { return _normal; }
|
|
|
|
bool _fn_complete(char match_first, char match_second) const
|
|
{
|
|
return _sequence_complete
|
|
&& _first == match_first
|
|
&& _second == match_second;
|
|
}
|
|
|
|
bool key_up() const { return _fn_complete(91, 65); }
|
|
bool key_down() const { return _fn_complete(91, 66); }
|
|
bool key_right() const { return _fn_complete(91, 67); }
|
|
bool key_left() const { return _fn_complete(91, 68); }
|
|
bool key_delete() const { return _fn_complete(91, 51); }
|
|
};
|
|
|
|
Seq_tracker _seq_tracker { };
|
|
|
|
void _write(char c) { _terminal.write(&c, sizeof(c)); }
|
|
|
|
void _write(char const *s) { _terminal.write(s, strlen(s)); }
|
|
|
|
void _write_spaces(unsigned num)
|
|
{
|
|
for (unsigned i = 0; i < num; i++)
|
|
_write(' ');
|
|
}
|
|
|
|
void _write_newline() { _write(10); }
|
|
|
|
void _clear_until_end_of_line() { _write("\e[K "); }
|
|
|
|
void _move_cursor_to(unsigned pos)
|
|
{
|
|
char seq[10];
|
|
snprintf(seq, sizeof(seq), "\e[%ldG", pos + _prompt_len);
|
|
_write(seq);
|
|
}
|
|
|
|
void _delete_character()
|
|
{
|
|
strncpy(&_buf[_cursor_pos], &_buf[_cursor_pos + 1], _buf_size);
|
|
|
|
_move_cursor_to(_cursor_pos);
|
|
_write(&_buf[_cursor_pos]);
|
|
_clear_until_end_of_line();
|
|
_move_cursor_to(_cursor_pos);
|
|
}
|
|
|
|
void _insert_character(char c)
|
|
{
|
|
/* insert regular character */
|
|
if (_cursor_pos >= _buf_size - 1)
|
|
return;
|
|
|
|
/* make room in the buffer */
|
|
for (unsigned i = _buf_size - 1; i > _cursor_pos; i--)
|
|
_buf[i] = _buf[i - 1];
|
|
_buf[_cursor_pos] = c;
|
|
|
|
/* update terminal */
|
|
_write(&_buf[_cursor_pos]);
|
|
_cursor_pos++;
|
|
_move_cursor_to(_cursor_pos);
|
|
}
|
|
|
|
void _fresh_prompt()
|
|
{
|
|
_write(_prompt);
|
|
_write(_buf);
|
|
_move_cursor_to(_cursor_pos);
|
|
}
|
|
|
|
void _handle_key()
|
|
{
|
|
enum { BACKSPACE = 8,
|
|
TAB = 9,
|
|
LINE_FEED = 10,
|
|
CARRIAGE_RETURN = 13 };
|
|
|
|
if (_seq_tracker.key_left()) {
|
|
if (_cursor_pos > 0) {
|
|
_cursor_pos--;
|
|
_write(BACKSPACE);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (_seq_tracker.key_right()) {
|
|
if (_cursor_pos < strlen(_buf)) {
|
|
_cursor_pos++;
|
|
_move_cursor_to(_cursor_pos);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (_seq_tracker.key_delete())
|
|
_delete_character();
|
|
|
|
if (!_seq_tracker.normal())
|
|
return;
|
|
|
|
char const c = _seq_tracker.normal_char();
|
|
|
|
if (c == TAB) {
|
|
_perform_completion();
|
|
return;
|
|
}
|
|
|
|
if (c == CARRIAGE_RETURN || c == LINE_FEED) {
|
|
if (strlen(_buf) > 0) {
|
|
_write(LINE_FEED);
|
|
_complete = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (c == BACKSPACE) {
|
|
if (_cursor_pos > 0) {
|
|
_cursor_pos--;
|
|
_delete_character();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (c == 126)
|
|
return;
|
|
|
|
_insert_character(c);
|
|
}
|
|
|
|
template <typename COMPLETABLE>
|
|
COMPLETABLE *_lookup_matching(char const *str, size_t str_len,
|
|
List<COMPLETABLE> &list)
|
|
{
|
|
Token tag(str, str_len);
|
|
COMPLETABLE *curr = list.first();
|
|
for (; curr; curr = curr->next()) {
|
|
if (strcmp(tag.start(), curr->name(), tag.len()) == 0
|
|
&& strlen(curr->name()) == tag.len())
|
|
return curr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Command *_lookup_matching_command()
|
|
{
|
|
Token cmd(_buf, _cursor_pos);
|
|
for (Command *curr = _commands.first(); curr; curr = curr->next())
|
|
if (strcmp(cmd.start(), curr->name().string(), cmd.len()) == 0
|
|
&& _cursor_pos > cmd.len())
|
|
return curr;
|
|
return nullptr;
|
|
}
|
|
|
|
template <typename T>
|
|
unsigned _num_partial_matches(char const *str, size_t str_len, List<T> &list)
|
|
{
|
|
Token token(str, str_len);
|
|
|
|
unsigned num_partial_matches = 0;
|
|
for (T *curr = list.first(); curr; curr = curr->next()) {
|
|
if (strcmp(token.start(), curr->name().string(), token.len()) != 0)
|
|
continue;
|
|
|
|
num_partial_matches++;
|
|
}
|
|
return num_partial_matches;
|
|
}
|
|
|
|
unsigned _num_matching_arguments(char const *str, size_t str_len,
|
|
Command const &command) const
|
|
{
|
|
unsigned num_matches = 0;
|
|
|
|
auto argument_fn = [&] (Argument const &arg) {
|
|
|
|
if (strcmp(arg.name().string(), str, str_len) == 0)
|
|
num_matches++;
|
|
};
|
|
|
|
command.for_each_argument(argument_fn);
|
|
|
|
return num_matches;
|
|
}
|
|
|
|
/**
|
|
* Determine the name-column width of list of partial matches
|
|
*/
|
|
template <typename T>
|
|
size_t _width_of_partial_matches(char const *str, size_t str_len,
|
|
List<T> &list)
|
|
{
|
|
Token token(str, str_len);
|
|
|
|
size_t max_name_len = 0;
|
|
for (T *curr = list.first(); curr; curr = curr->next()) {
|
|
if (strcmp(token.start(), curr->name().string(), token.len()) != 0)
|
|
continue;
|
|
|
|
size_t const name_len = strlen(curr->name().string())
|
|
+ strlen(curr->name_suffix());
|
|
max_name_len = max(max_name_len, name_len);
|
|
}
|
|
return max_name_len;
|
|
}
|
|
|
|
unsigned _width_of_matching_arguments(char const *str, size_t str_len,
|
|
Command const &command) const
|
|
{
|
|
size_t max_name_len = 0;
|
|
|
|
auto argument_fn = [&] (Argument const &arg) {
|
|
|
|
if (strcmp(arg.name().string(), str, str_len) == 0) {
|
|
size_t const name_len = strlen(arg.name().string());
|
|
if (name_len > max_name_len)
|
|
max_name_len = name_len;
|
|
}
|
|
};
|
|
|
|
command.for_each_argument(argument_fn);
|
|
|
|
return max_name_len;
|
|
}
|
|
|
|
template <typename T>
|
|
char const *_any_partial_match_name(char const *str, size_t str_len,
|
|
List<T> &list)
|
|
{
|
|
Token token(str, str_len);
|
|
|
|
for (T *curr = list.first(); curr; curr = curr->next())
|
|
if (strcmp(token.start(), curr->name().string(), token.len()) == 0)
|
|
return curr->name().string();
|
|
|
|
return 0;
|
|
}
|
|
|
|
Argument::Name _any_matching_argument(char const *str, size_t str_len,
|
|
Command const &command) const
|
|
{
|
|
Argument::Name name;
|
|
|
|
auto argument_fn = [&] (Argument const &arg) {
|
|
|
|
if (strcmp(arg.name().string(), str, str_len) == 0)
|
|
name = arg.name();
|
|
};
|
|
|
|
command.for_each_argument(argument_fn);
|
|
|
|
return name;
|
|
}
|
|
|
|
template <typename T>
|
|
void _list_partial_matches(char const *str, size_t str_len,
|
|
unsigned pad, List<T> &list)
|
|
{
|
|
Token token(str, str_len);
|
|
|
|
for (T *curr = list.first(); curr; curr = curr->next()) {
|
|
if (strcmp(token.start(), curr->name().string(), token.len()) != 0)
|
|
continue;
|
|
|
|
_write_newline();
|
|
_write_spaces(2);
|
|
_write(curr->name().string());
|
|
_write_spaces(1);
|
|
_write(curr->name_suffix());
|
|
|
|
/* pad short help with whitespaces */
|
|
size_t const name_len = strlen(curr->name().string())
|
|
+ strlen(curr->name_suffix());
|
|
_write_spaces(pad + 3 - name_len);
|
|
_write(curr->short_help().string());
|
|
}
|
|
}
|
|
|
|
void _list_matching_arguments(char const *str, size_t str_len,
|
|
unsigned pad, Command const &command)
|
|
{
|
|
auto argument_fn = [&] (Argument const &arg) {
|
|
|
|
if (strcmp(arg.name().string(), str, str_len) == 0) {
|
|
|
|
_write_newline();
|
|
_write_spaces(2);
|
|
_write(arg.name().string());
|
|
_write_spaces(1);
|
|
_write(arg.name_suffix());
|
|
|
|
/* pad short help with whitespaces */
|
|
size_t const name_len = strlen(arg.name().string())
|
|
+ strlen(arg.name_suffix());
|
|
_write_spaces(pad + 3 - name_len);
|
|
_write(arg.short_help().string());
|
|
}
|
|
};
|
|
|
|
command.for_each_argument(argument_fn);
|
|
}
|
|
|
|
template <typename T>
|
|
void _do_completion(char const *str, size_t str_len, List<T> &list)
|
|
{
|
|
Token token(str, str_len);
|
|
|
|
/* look up completable token */
|
|
T *partial_match = 0;
|
|
for (T *curr = list.first(); curr; curr = curr->next()) {
|
|
if (strcmp(token.start(), curr->name().string(), token.len()) == 0) {
|
|
partial_match = curr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!partial_match)
|
|
return;
|
|
|
|
for (unsigned i = token.len(); i < strlen(partial_match->name().string()); i++)
|
|
_insert_character(partial_match->name().string()[i]);
|
|
|
|
_insert_character(' ');
|
|
}
|
|
|
|
void _do_argument_completion(char const *str, size_t str_len,
|
|
Command const &command)
|
|
{
|
|
Argument::Name partial_match;
|
|
|
|
auto argument_fn = [&] (Argument const &arg) {
|
|
|
|
if (strcmp(arg.name().string(), str, str_len) == 0)
|
|
partial_match = arg.name();
|
|
};
|
|
|
|
command.for_each_argument(argument_fn);
|
|
|
|
for (unsigned i = str_len; i < strlen(partial_match.string()); i++)
|
|
_insert_character(partial_match.string()[i]);
|
|
|
|
_insert_character(' ');
|
|
}
|
|
|
|
void _complete_argument(char const *str, size_t str_len, Command &command)
|
|
{
|
|
unsigned const matching_parameters =
|
|
_num_partial_matches(str, str_len, command.parameters());
|
|
|
|
unsigned const matching_arguments =
|
|
_num_matching_arguments(str, str_len, command);
|
|
|
|
/* matches are ambiguous */
|
|
if (matching_arguments + matching_parameters > 1) {
|
|
|
|
/*
|
|
* Try to complete additional characters that are common among
|
|
* all matches.
|
|
*/
|
|
char buf[Completable::Name::size()];
|
|
strncpy(buf, str, Genode::min(sizeof(buf), str_len + 1));
|
|
|
|
/* pick any representative as a template to take characters from */
|
|
char const *name = _any_partial_match_name(str, str_len, command.parameters());
|
|
Argument::Name arg_name;
|
|
if (!name) {
|
|
arg_name = _any_matching_argument(str, str_len, command);
|
|
if (strlen(arg_name.string()))
|
|
name = arg_name.string();
|
|
}
|
|
|
|
size_t i = str_len;
|
|
for (; (i < sizeof(buf) - 1) && (i < strlen(name)); i++) {
|
|
|
|
buf[i + 0] = name[i];
|
|
buf[i + 1] = 0;
|
|
|
|
if (matching_parameters !=
|
|
_num_partial_matches(buf, i + 1, command.parameters()))
|
|
break;
|
|
|
|
if (matching_arguments !=
|
|
_num_matching_arguments(buf, i + 1, command))
|
|
break;
|
|
|
|
_insert_character(buf[i]);
|
|
}
|
|
|
|
/*
|
|
* If we managed to do a partial completion, let's yield
|
|
* control to the user.
|
|
*/
|
|
if (i > str_len)
|
|
return;
|
|
|
|
/*
|
|
* No automatic completion was possible, print list of possible
|
|
* parameters and arguments
|
|
*/
|
|
size_t const pad =
|
|
max(_width_of_partial_matches(str, str_len, command.parameters()),
|
|
_width_of_matching_arguments(str, str_len, command));
|
|
|
|
_list_partial_matches(str, str_len, pad, command.parameters());
|
|
_list_matching_arguments(str, str_len, pad, command);
|
|
|
|
_write_newline();
|
|
_fresh_prompt();
|
|
|
|
return;
|
|
}
|
|
|
|
if (matching_parameters == 1)
|
|
_do_completion(str, str_len, command.parameters());
|
|
|
|
if (matching_arguments == 1)
|
|
_do_argument_completion(str, str_len, command);
|
|
}
|
|
|
|
void _perform_completion()
|
|
{
|
|
Command *command = _lookup_matching_command();
|
|
|
|
if (!command) {
|
|
unsigned const matches = _num_partial_matches(_buf, _cursor_pos, _commands);
|
|
|
|
if (matches == 1)
|
|
_do_completion(_buf, _cursor_pos, _commands);
|
|
|
|
if (matches > 1) {
|
|
unsigned const pad =
|
|
_width_of_partial_matches(_buf, _cursor_pos, _commands);
|
|
_list_partial_matches(_buf, _cursor_pos, pad, _commands);
|
|
_write_newline();
|
|
_fresh_prompt();
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We hava a valid command, now try to complete the parameters...
|
|
*/
|
|
|
|
/* determine token under the cursor */
|
|
Argument_tracker argument_tracker(*command);
|
|
|
|
Token token(_buf, _buf_size);
|
|
for (; token; token = token.next()) {
|
|
|
|
argument_tracker.supply_token(token, true);
|
|
|
|
if (!argument_tracker.valid())
|
|
return;
|
|
|
|
unsigned long const token_pos = (unsigned long)(token.start() - _buf);
|
|
|
|
/* we have reached the token under the cursor */
|
|
if (token.type() == Token::IDENT
|
|
&& _cursor_pos >= token_pos
|
|
&& _cursor_pos <= token_pos + token.len()) {
|
|
|
|
if (argument_tracker.expect_arg()) {
|
|
char const *start = token.start();
|
|
size_t const len = _cursor_pos - token_pos;
|
|
|
|
_complete_argument(start, len, *command);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* the cursor is positioned at beginning of new argument */
|
|
if (argument_tracker.expect_arg())
|
|
_complete_argument("", 0, *command);
|
|
|
|
if (argument_tracker.expect_space())
|
|
_insert_character(' ');
|
|
}
|
|
|
|
public:
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* \param prompt string to be printed at the beginning of the line
|
|
* \param buf destination buffer
|
|
* \param buf_size destination buffer size
|
|
* \param terminal terminal used as output device
|
|
* \param commands meta information about commands and their arguments
|
|
*/
|
|
Line_editor(char const *prompt, char *buf, size_t buf_size,
|
|
Terminal::Session &terminal, Command_registry &commands)
|
|
:
|
|
_prompt(prompt), _prompt_len(strlen(prompt)),
|
|
_buf(buf), _buf_size(buf_size),
|
|
_terminal(terminal), _commands(commands)
|
|
{
|
|
reset();
|
|
}
|
|
|
|
/**
|
|
* Reset prompt to initial state after construction
|
|
*/
|
|
void reset()
|
|
{
|
|
_buf[0] = 0;
|
|
_complete = false;
|
|
_cursor_pos = 0;
|
|
_seq_tracker = Seq_tracker();
|
|
_fresh_prompt();
|
|
}
|
|
|
|
/**
|
|
* Supply a character of user input
|
|
*/
|
|
void submit_input(char c)
|
|
{
|
|
_seq_tracker.input(c);
|
|
_handle_key();
|
|
}
|
|
|
|
/**
|
|
* Returns true if the editing is complete, i.e., the user pressed the
|
|
* return key.
|
|
*/
|
|
bool completed() const { return _complete; }
|
|
};
|
|
|
|
#endif /* _LINE_EDITOR_H_ */
|