Radish alpha
r
rad:z39mP9rQAaGmERfUMPULfPUi473tY
Radicle terminal user interface
Radicle
Git
Logging and state persistance improvements
Merged did:key:z6MkgFq6...nBGz opened 3 months ago
12 files changed +77 -62 c6ef29c5 595f2cc4
modified bin/commands/inbox.rs
@@ -188,7 +188,7 @@ pub async fn run(options: Options, ctx: impl radicle_cli::terminal::Context) ->
            if let Err(err) = crate::log::enable() {
                println!("{err}");
            }
-
            log::info!("Starting inbox listing interface in project {rid}..");
+
            log::info!("Starting inbox listing app in project {rid}..");

            let mut state = PreviousState::default();
            loop {
@@ -213,9 +213,7 @@ pub async fn run(options: Options, ctx: impl radicle_cli::terminal::Context) ->
                        .map(|o| serde_json::to_string(&o).unwrap_or_default())
                        .unwrap_or_default();

-
                    log::info!("About to print to `stderr`: {selection}");
-
                    log::info!("Exiting inbox listing interface..");
-

+
                    log::info!("Exiting inbox listing app..");
                    eprint!("{selection}");
                } else if let Some(selection) = selection {
                    if let Some(operation) = selection.operation.clone() {
modified bin/commands/issue.rs
@@ -250,7 +250,7 @@ pub async fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> {
            if let Err(err) = crate::log::enable() {
                println!("{err}");
            }
-
            log::info!("Starting issue listing interface in project {rid}..");
+
            log::info!("Starting issue listing app in project {rid}..");

            #[derive(Default)]
            struct PreviousState {
@@ -286,8 +286,7 @@ pub async fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> {
                        .map(|o| serde_json::to_string(&o).unwrap_or_default())
                        .unwrap_or_default();

-
                    log::info!("Exiting issue listing interface..");
-

+
                    log::info!("Exiting issue listing app..");
                    eprint!("{selection}");
                } else if let Some(selection) = selection {
                    if let Some(operation) = selection.operation.clone() {
modified bin/commands/patch.rs
@@ -307,13 +307,13 @@ pub async fn run(options: Options, ctx: impl radicle_cli::terminal::Context) ->

    match options.op {
        Operation::List { opts } => {
-
            log::info!("Starting patch selection interface in project {rid}..");
+
            log::info!("Starting patch listing app in project {rid}..");

            let rid = options.repo.unwrap_or(rid);
            interface::list(opts.clone(), ctx.profile()?, rid).await?;
        }
        Operation::Review { ref opts } => {
-
            log::info!("Starting patch review interface in project {rid}..");
+
            log::info!("Starting patch review app in project {rid}..");

            let profile = ctx.profile()?;
            let rid = options.repo.unwrap_or(rid);
@@ -391,8 +391,7 @@ mod interface {
                    .map(|o| serde_json::to_string(&o).unwrap_or_default())
                    .unwrap_or_default();

-
                log::info!("About to print to `stderr`: {selection}");
-
                log::info!("Exiting patch list interface..");
+
                log::info!("Exiting patch list app..");

                eprint!("{selection}");

modified bin/commands/patch/review.rs
@@ -595,8 +595,6 @@ impl store::Update<Message> for App<'_> {
    type Return = Response;

    fn update(&mut self, message: Message) -> Option<Exit<Self::Return>> {
-
        log::info!("Received message: {message:?}");
-

        match message {
            Message::ShowMain => {
                let mut state = self.state.lock().unwrap();
@@ -641,15 +639,15 @@ impl store::Update<Message> for App<'_> {
            }
            Message::Accept => {
                match self.accept_selected_hunk() {
-
                    Ok(()) => log::info!("Accepted selected hunk."),
-
                    Err(err) => log::info!("An error occured while accepting hunk: {err}"),
+
                    Ok(()) => log::debug!("Accepted selected hunk ({:?}).", self.selected_hunk()),
+
                    Err(err) => log::error!("An error occured while accepting hunk: {err}"),
                }
                None
            }
            Message::Reject => {
                match self.reject_selected_hunk() {
-
                    Ok(()) => log::info!("Rejected selected hunk."),
-
                    Err(err) => log::info!("An error occured while rejecting hunk: {err}"),
+
                    Ok(()) => log::debug!("Rejected selected hunk ({:?}).", self.selected_hunk()),
+
                    Err(err) => log::error!("An error occured while rejecting hunk: {err}"),
                }
                None
            }
modified bin/log.rs
@@ -1,32 +1,23 @@
use std::fs;
-
use std::time::SystemTime;
-

-
use anyhow::bail;
-

-
use homedir::my_home;

use log::LevelFilter;

-
const PATH: &str = ".radicle-tui/logs";
-
const PREFIX: &str = "rad-tui-";
+
use crate::settings;

-
/// Enables logging to `$HOME/.radicle-tui/logs/`. Creates folder if it does not
-
/// exist.
-
pub fn enable() -> Result<(), anyhow::Error> {
-
    match my_home()? {
-
        Some(home) => {
-
            let path = format!("{}/{}", home.to_string_lossy(), PATH);
-
            let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
+
const FILE_PREFIX: &str = "rad-tui";

+
pub fn enable() -> Result<(), anyhow::Error> {
+
    match settings::get_state_path() {
+
        Ok(path) => {
            fs::create_dir_all(path.clone())?;

-
            simple_logging::log_to_file(
-
                format!("{path}/{PREFIX}{}.log", now.as_millis()),
-
                LevelFilter::Info,
-
            )?;
+
            let file = fs::OpenOptions::new()
+
                .append(true)
+
                .open(format!("{}/{FILE_PREFIX}.log", path.to_string_lossy()))?;
+
            simple_logging::log_to(file, LevelFilter::Info);

            Ok(())
        }
-
        None => bail!("Failed to read home directory"),
+
        Err(err) => Err(anyhow::Error::from(err)),
    }
}
modified bin/settings.rs
@@ -1,4 +1,10 @@
use std::collections::HashMap;
+
use std::env;
+
use std::path::PathBuf;
+

+
use thiserror::Error;
+

+
use homedir::my_home;

use radicle_tui as tui;
use tui::ui::theme::Theme;
@@ -7,6 +13,14 @@ static THEME_RADICLE: &str = "Radicle";

pub type ThemeBundleId = String;

+
#[derive(Error, Debug)]
+
pub enum Error {
+
    #[error(
+
        "could not resolve home directory ($HOME is not set and `/etc/passwd` does not resolve)"
+
    )]
+
    Home,
+
}
+

/// `ThemeMode` defines which theme is selected from a `ThemeBundle`. It can
/// be either `light``, `dark`` or `auto``, which sets the mode depending on
/// the terminal background luma.
@@ -72,3 +86,19 @@ impl Default for Settings {
        }
    }
}
+

+
pub fn get_state_path() -> Result<PathBuf, Error> {
+
    let base = match env::var("XDG_STATE_HOME") {
+
        Ok(path) => PathBuf::from(path),
+
        Err(err) => {
+
            log::debug!("Could not read `XDG_STATE_HOME`: {err}");
+
            my_home()
+
                .ok()
+
                .flatten()
+
                .ok_or(Error::Home)?
+
                .join(".local/state")
+
        }
+
    };
+

+
    Ok(base.join("radicle-tui"))
+
}
modified bin/state.rs
@@ -3,14 +3,12 @@ use std::{fmt::Display, fs};

use anyhow::Result;

-
use homedir::my_home;
-

use serde::{Deserialize, Serialize};

use radicle::cob::ObjectId;
use radicle::identity::RepoId;

-
const PATH: &str = ".radicle-tui/states";
+
use crate::settings;

/// Converts bytes to a deserializable type.
pub fn from_json<'a, D>(bytes: &'a [u8]) -> Result<D>
@@ -44,18 +42,20 @@ pub struct FileStore {
}

impl FileStore {
-
    pub fn new(filename: impl ToString) -> Result<Self> {
-
        let folder = match my_home()? {
-
            Some(home) => format!("{}/{}", home.to_string_lossy(), PATH),
-
            _ => anyhow::bail!("Failed to read home directory"),
-
        };
-
        let path = format!("{folder}/{}.json", filename.to_string());
-

-
        fs::create_dir_all(folder.clone())?;
-

-
        Ok(Self {
-
            path: PathBuf::from(path),
-
        })
+
    pub fn new(filename: impl ToString) -> Result<Self, anyhow::Error> {
+
        match settings::get_state_path() {
+
            Ok(path) => {
+
                fs::create_dir_all(path.clone())?;
+
                Ok(Self {
+
                    path: PathBuf::from(format!(
+
                        "{}/{}.json",
+
                        path.to_string_lossy(),
+
                        filename.to_string()
+
                    )),
+
                })
+
            }
+
            Err(err) => Err(anyhow::Error::from(err)),
+
        }
    }
}

modified bin/ui/items/notification.rs
@@ -176,8 +176,8 @@ impl NotificationKind {
                } else if typed_id.is_identity() {
                    let Ok(identity) = Identity::get(id, repo) else {
                        log::error!(
-
                            target: "items",
-
                            "Error retrieving identity {id} for notification {}", notification.id
+
                            "Error retrieving identity {id} for notification {}",
+
                            notification.id
                        );
                        return Ok(None);
                    };
@@ -187,8 +187,8 @@ impl NotificationKind {
                        .and_then(|id| identity.revision(&id))
                    else {
                        log::error!(
-
                            target: "items",
-
                            "Error retrieving identity revision for notification {}", notification.id
+
                            "Error retrieving identity revision for notification {}",
+
                            notification.id
                        );
                        return Ok(None);
                    };
modified bin/ui/items/patch.rs
@@ -613,7 +613,7 @@ impl From<(&Repository, &Review, &HunkDiff)> for HunkItem<'_> {
                            let ranges = DiffLineRanges::from(hunk);
                            let index = DiffLineIndex::from(location);

-
                            log::warn!("Checking comment {comment:?} at {index:?}");
+
                            log::debug!("Checking comment {comment:?} at {index:?}");

                            return index.is_start_of(&ranges)
                                || index.is_inside_of(&ranges)
modified src/task.rs
@@ -62,7 +62,7 @@ where
                    for mut p in processors.clone() {
                        for m in p.process(message.clone()).await? {
                            if let Err(err) = self.work_tx.send(m) {
-
                                log::error!(target: "worker", "Unable to send message: {err}")
+
                                log::error!("Unable to send message: {err}")
                            }
                        }
                    }
modified src/terminal.rs
@@ -111,7 +111,7 @@ impl StdinReader {
                            }
                        }
                        Err(err) => {
-
                            log::error!(target: "terminal", "Could not read from stdin: {err}");
+
                            log::error!("Could not read from stdin: {err}");
                        }
                    },
                    _ => {
modified src/ui.rs
@@ -69,15 +69,15 @@ impl Frontend {
                Some(event) = event_rx.recv() => {
                    match event {
                        Event::Key(key) => {
-
                            log::debug!(target: "frontend", "Received key event: {key:?}");
+
                            log::debug!("Received key event: {key:?}");
                            ctx.store_input(event)
                        }
                        Event::Resize(x, y) => {
-
                            log::debug!(target: "frontend", "Received resize event: {x},{y}");
+
                            log::debug!("Received resize event: {x},{y}");
                            terminal.clear()?;
                        },
                        Event::Unknown => {
-
                            log::debug!(target: "frontend", "Received unknown event")
+
                            log::debug!("Received unknown event")
                        }
                    }
                },
@@ -94,7 +94,7 @@ impl Frontend {
                let ctx = ctx.clone().with_frame_size(frame.area());

                if let Err(err) = state.show(&ctx, frame) {
-
                    log::warn!("Drawing failed: {err}");
+
                    log::error!("Drawing failed: {err}");
                }
            })?;