Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ref: https://docs.codecov.com/docs/codecovyml-reference
coverage:
# Hold ourselves to a high bar
range: 85..100
round: down
precision: 1
status:
# ref: https://docs.codecov.com/docs/commit-status
project:
default:
# Avoid false negatives
threshold: 1%

# Test files aren't important for coverage
ignore:
- "tests"

# Make comments less noisy
comment:
layout: "files"
require_changes: true
30 changes: 30 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,33 @@ jobs:
toolchain: stable
- name: cargo test --locked
run: cargo test --locked --all-features

coverage:
runs-on: ubuntu-latest
name: ubuntu / stable / coverage
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2
with:
submodules: true
- name: Install stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # branch=master
with:
toolchain: stable
components: llvm-tools-preview
- name: cargo install cargo-llvm-cov
uses: taiki-e/install-action@68675c5a5f1a6950c3975d33f3ae0ef155e5bf3d # tag=v2.68.15
with:
tool: cargo-llvm-cov
- name: cargo generate-lockfile
if: hashFiles('Cargo.lock') == ''
run: cargo generate-lockfile
- name: cargo llvm-cov
run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info
- name: Record Rust version
run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV"
- name: Upload to codecov.io
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # tag=v5.5.2
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
env_vars: OS,RUST
7 changes: 7 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ use crate::formats::rmp::BinaryWriter;

#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Event format for entries in the history file.
pub struct Event {
/// time when execution of the command began
pub timestamp_millis: i64,
pub command: String,
/// records time when the command ended (can be used to calculate duration)
pub endtime: i64,
pub exit_code: i16,
pub folder: String,
/// a special machine id to filter by machine
pub machine: String,
/// a special session id to filter by session
pub session: String,
}

Expand All @@ -38,6 +43,8 @@ impl Arbitrary<'_> for Event {

#[allow(deprecated)]
impl From<JsonLineEvent> for Event {
/// Converts a `JsonLineEvent` to the new binary format. The `JsonLineEven` format is deprecated
/// and this is only used to convert old history files to the binary format.
fn from(event: JsonLineEvent) -> Self {
let timestamp = event.timestamp.timestamp_millis();
Self {
Expand Down
3 changes: 3 additions & 0 deletions src/formats.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
//! Supported history file formats: [`json_lines`] (deprecated) and [`rmp`] (current binary).
pub mod json_lines;
pub mod rmp;

/// all formats we support
pub enum Kind {
JsonLines,
Rmp,
}

impl Kind {
/// get file extension for a format kind
pub fn extension(&self) -> String {
match self {
Kind::JsonLines => "osh".to_string(),
Expand Down
3 changes: 3 additions & 0 deletions src/formats/json_lines.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Deprecated JSON-per-line format with a header. Only used to migrate old `.osh` files to the
//! current binary format.
#![allow(deprecated)]

use chrono::{DateTime, Local};
Expand Down Expand Up @@ -69,6 +71,7 @@ impl Entry {
}
}

/// parse and collect all [`JsonLineEvent`]s in the slice
pub fn load_osh_events(data: &[u8]) -> std::io::Result<Vec<JsonLineEvent>> {
let mut events = Vec::new();
for line in data.split(|c| *c == b'\n') {
Expand Down
4 changes: 4 additions & 0 deletions src/formats/rmp.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! Binary format using `rmp_serde`. Wire layout: each record is an 8-byte LE length prefix
//! followed by a msgpack-encoded [`Event`]. This allows O(1) appends without deserialising the
//! whole file.
use std::io::Write;

use rmp_serde::{decode, encode::to_vec};
Expand Down Expand Up @@ -29,6 +32,7 @@ impl<W: Write> BinaryWriter<W> {
}
}

/// parse and collect all [`Event`]s in the slice
pub fn load_osh_events(data: &[u8]) -> std::io::Result<Vec<Event>> {
let mut events = Vec::new();
let mut cursor = 0;
Expand Down
9 changes: 6 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ pub mod formats;
pub mod matcher;
pub mod ui;

pub fn mmap(f: &File) -> &'_ [u8] {
/// memory map `file`
pub fn mmap(file: &File) -> &'_ [u8] {
#[allow(clippy::unwrap_used)]
let len = f.metadata().unwrap().len();
let len = file.metadata().unwrap().len();
unsafe {
let ptr = libc::mmap(
std::ptr::null_mut(),
len as libc::size_t,
libc::PROT_READ,
libc::MAP_SHARED,
f.as_raw_fd(),
file.as_raw_fd(),
0,
);
if ptr == libc::MAP_FAILED {
Expand All @@ -41,6 +42,7 @@ pub fn mmap(f: &File) -> &'_ [u8] {
}
}

/// discover all parsable osh files under `~/.osh` for a specific format
pub fn osh_files(kind: formats::Kind) -> anyhow::Result<HashSet<PathBuf>> {
// TODO when can this really fail?
let home_dir = home::home_dir().ok_or(anyhow!("no home directory"))?;
Expand All @@ -60,6 +62,7 @@ pub fn osh_files(kind: formats::Kind) -> anyhow::Result<HashSet<PathBuf>> {
Ok(files)
}

/// load all binary osh files in `~/.osh` and return a merged and sorted vector of all events
pub fn load_sorted() -> anyhow::Result<Vec<Event>> {
let oshs = osh_files(Kind::Rmp)?;
let osh_files: Vec<File> = oshs
Expand Down
34 changes: 12 additions & 22 deletions src/ui.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Ratatui-based TUI. Entry point is [`Tui::start`].
use std::{
collections::HashSet,
fmt::Display,
Expand Down Expand Up @@ -111,6 +112,8 @@ impl FromStr for EventFilter {
pub struct Tui;

impl Tui {
/// Set up the terminal and run the TUI. `receiver` is fed [`Event`]s by the caller.
/// Returns the selected event, if any.
pub fn start(
receiver: Receiver<Arc<Event>>,
query: &str,
Expand Down Expand Up @@ -155,26 +158,25 @@ impl Tui {
}
}

/// App holds the state of the application
/// app holds the state of the application
struct App {
/// Current value of the input box
/// current value of the input box
input: String,
/// Position of cursor in the editor area.
/// position of cursor in the editor area.
character_index: usize,
/// History of recorded messages
/// display entries: (time-ago label, command) pairs derived from `events`
history: Vec<(String, String)>,
/// indices into history sorted according to fuzzer score if we have a query
indexer: FuzzyIndex,
/// Reader for collecting events from background thread
/// reader for collecting events from background thread
reader: EventReader,
/// Accumulated events pool for filtering and matching
/// accumulated events pool for filtering and matching
events: Vec<Arc<Event>>,
/// Currently selected index in the history widget (0 = bottom-most)
/// currently selected index in the history widget (0 = bottom-most)
selected_index: usize,
/// currently active event filter
filters: HashSet<EventFilter>,
folder: String,
/// Current session id
session_id: Option<String>,
show_score: bool,
}
Expand Down Expand Up @@ -291,10 +293,7 @@ impl App {
self.run_matcher();
}

/// Returns the byte index based on the character position.
///
/// Since each character in a string can contain multiple bytes, it's necessary to calculate
/// the byte index based on the index of the character.
/// returns the byte index for `character_index`, which counts Unicode scalar values not bytes.
fn byte_index(&self) -> usize {
self.input
.char_indices()
Expand All @@ -306,20 +305,11 @@ impl App {
fn delete_char(&mut self) {
let is_not_cursor_leftmost = self.character_index != 0;
if is_not_cursor_leftmost {
// Method "remove" is not used on the saved text for deleting the selected char.
// Reason: Using remove on String works on bytes instead of the chars.
// Using remove would require special care because of char boundaries.

// String::remove works on bytes, not chars; reconstruct around the character instead.
let current_index = self.character_index;
let from_left_to_current_index = current_index - 1;

// Getting all characters before the selected character.
let before_char_to_delete = self.input.chars().take(from_left_to_current_index);
// Getting all characters after selected character.
let after_char_to_delete = self.input.chars().skip(current_index);

// Put all characters together except the selected one.
// By leaving the selected one out, it is forgotten and therefore deleted.
self.input = before_char_to_delete.chain(after_char_to_delete).collect();
self.move_cursor_left();
}
Expand Down
Loading