Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 22736c01 authored by Sonny Sasaka's avatar Sonny Sasaka
Browse files

floss: client: Add basic help command and autocomplete

This adds help and autocomplete to btclient. Currently only supports
list of commands to be displayed or autocompleted.

Bug: 188718349
Tag: #floss
Test: manual - Ran btclient, type a string and Tab.

Change-Id: I27f112427f453649d786defb89fb3c21231bbb4c
parent 86c381bf
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ edition = "2018"

[dependencies]
rustyline = "8.0"
rustyline-derive = "0.4.0"
bt_topshim = { path = "../../topshim" }
btstack = { path = "../stack" }
manager_service = { path = "../mgmt" }
+19 −1
Original line number Diff line number Diff line
@@ -65,16 +65,34 @@ pub struct CommandHandler<TBluetoothManager: IBluetoothManager, TBluetooth: IBlu
    bluetooth: Arc<Mutex<Box<TBluetooth>>>,

    is_bluetooth_callback_registered: bool,

    commands: Vec<String>,
}

impl<TBluetoothManager: IBluetoothManager, TBluetooth: IBluetooth>
    CommandHandler<TBluetoothManager, TBluetooth>
{
    /// Creates a new CommandHandler.
    ///
    /// * `commands` - List of commands for `help`.
    pub fn new(
        bluetooth_manager: Arc<Mutex<Box<TBluetoothManager>>>,
        bluetooth: Arc<Mutex<Box<TBluetooth>>>,
        commands: Vec<String>,
    ) -> CommandHandler<TBluetoothManager, TBluetooth> {
        CommandHandler { bluetooth_manager, bluetooth, is_bluetooth_callback_registered: false }
        CommandHandler {
            bluetooth_manager,
            bluetooth,
            is_bluetooth_callback_registered: false,
            commands,
        }
    }

    pub fn cmd_help(&self) {
        println!("Available commands:");
        for cmd in &self.commands {
            println!("{}", cmd);
        }
    }

    pub fn cmd_enable(&self, _cmd: String) {
+56 −6
Original line number Diff line number Diff line
@@ -2,7 +2,13 @@

use futures::Future;

use rustyline::{Config, Editor};
use rustyline::completion::Completer;
use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter;
use rustyline::hint::Hinter;
use rustyline::validate::Validator;
use rustyline::{CompletionType, Config, Editor};
use rustyline_derive::Helper;

use std::pin::Pin;
use std::sync::{Arc, Mutex};
@@ -10,13 +16,50 @@ use std::task::{Context, Poll};

use crate::console_blue;

#[derive(Helper)]
struct BtHelper {
    commands: Vec<String>,
}

impl Completer for BtHelper {
    type Candidate = String;

    // Returns completion based on supported commands.
    // TODO: Add support to autocomplete BT address, command parameters, etc.
    fn complete(
        &self,
        line: &str,
        pos: usize,
        _ctx: &rustyline::Context<'_>,
    ) -> Result<(usize, Vec<String>), ReadlineError> {
        let slice = &line[..pos];
        let mut completions = vec![];

        for cmd in self.commands.iter() {
            if cmd.starts_with(slice) {
                completions.push(cmd.clone());
            }
        }

        Ok((0, completions))
    }
}

impl Hinter for BtHelper {
    type Hint = String;
}

impl Highlighter for BtHelper {}

impl Validator for BtHelper {}

/// A future that does async readline().
///
/// async readline() is implemented by spawning a thread for the blocking readline(). While this
/// readline() thread is blocked, it yields back to executor and will wake the executor up when the
/// blocked thread has proceeded and got input from readline().
pub struct AsyncReadline {
    rl: Arc<Mutex<Editor<()>>>,
    rl: Arc<Mutex<Editor<BtHelper>>>,
    result: Arc<Mutex<Option<rustyline::Result<String>>>>,
}

@@ -44,15 +87,22 @@ impl Future for AsyncReadline {

/// Wrapper of rustyline editor that supports async readline().
pub struct AsyncEditor {
    rl: Arc<Mutex<Editor<()>>>,
    rl: Arc<Mutex<Editor<BtHelper>>>,
}

impl AsyncEditor {
    /// Creates new async rustyline editor.
    pub fn new() -> AsyncEditor {
        let builder = Config::builder().auto_add_history(true).history_ignore_dups(true);
    ///
    /// * `commands` - List of commands for autocomplete.
    pub fn new(commands: Vec<String>) -> AsyncEditor {
        let builder = Config::builder()
            .auto_add_history(true)
            .history_ignore_dups(true)
            .completion_type(CompletionType::List);
        let config = builder.build();
        let rl = rustyline::Editor::<()>::with_config(config);
        let mut rl = rustyline::Editor::with_config(config);
        let helper = BtHelper { commands };
        rl.set_helper(Some(helper));
        AsyncEditor { rl: Arc::new(Mutex::new(rl)) }
    }

+19 −2
Original line number Diff line number Diff line
@@ -98,9 +98,26 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
            objpath: String::from("/org/chromium/bluetooth/client/bluetooth_manager_callback"),
        }));

        let mut handler = CommandHandler::new(api.bluetooth_manager.clone(), api.bluetooth.clone());
        // TODO(b/193719802): Refactor this into a dedicated "Help" data structure.
        let commands = vec![
            String::from("help"),
            String::from("enable"),
            String::from("disable"),
            String::from("get_address"),
            String::from("start_discovery"),
            String::from("cancel_discovery"),
            String::from("create_bond"),
        ];

        let mut handler = CommandHandler::new(
            api.bluetooth_manager.clone(),
            api.bluetooth.clone(),
            commands.clone(),
        );

        let mut handle_cmd = move |cmd: String| match cmd.split(' ').collect::<Vec<&str>>()[0] {
            "help" => handler.cmd_help(),

            "enable" => handler.cmd_enable(cmd),
            "disable" => handler.cmd_disable(cmd),
            "get_address" => handler.cmd_get_address(cmd),
@@ -115,7 +132,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
            _ => print_info!("Command \"{}\" not recognized", cmd),
        };

        let editor = AsyncEditor::new();
        let editor = AsyncEditor::new(commands.clone());

        loop {
            let result = editor.readline().await;