Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add newline support #162

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
17 changes: 6 additions & 11 deletions mcfly.bash
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ MCFLY_SESSION_ID="$(command dd if=/dev/urandom bs=256 count=1 2> /dev/null | LC_
export MCFLY_SESSION_ID

# Find the binary
MCFLY_PATH=${MCFLY_PATH:-$(which mcfly)}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was definitely a reason for $MCFLY_PATH to exist, but I can't recall anymore what it was.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just leave it out then? I can't find anything that reads it, just things setting it in the dev.* scripts.

if [ -z "$MCFLY_PATH" ]; then
if [ -z "$(which mcfly)" ]; then
echo "Cannot find the mcfly binary, please make sure that mcfly is in your path before sourcing mcfly.bash."
return 1
fi
Expand All @@ -44,15 +43,11 @@ function mcfly_prompt_command {
command tail -n100 "${HISTFILE}" >| "${MCFLY_HISTORY}"
fi

history -a "${MCFLY_HISTORY}" # Append history to $MCFLY_HISTORY.
# Run mcfly with the saved code. It will:
# * append commands to $HISTFILE, (~/.bash_history by default)
# for backwards compatibility and to load in new terminal sessions;
# * find the text of the last command in $MCFLY_HISTORY and save it to the database.
$MCFLY_PATH add --exit ${exit_code} --append-to-histfile
# Clear the in-memory history and reload it from $MCFLY_HISTORY
# (to remove instances of '#mcfly: ' from the local session history).
history -cr "${MCFLY_HISTORY}"
# Run mcfly with the last history entry on stdin. It will:
# * Append the command to $HISTFILE, (~/.bash_history by default)
# * Parse out the command and and save it to the database.
HISTTIMEFORMAT="%s:" history 1 | mcfly add --exit $exit_code --command-from-stdin
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this still work on bash <4?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the bash release notes, HISTTIMEFORMAT was added in Bash 3, released in 2004. I did a quick test to be sure, it behaves the same as bash 4. Should be good.


return ${exit_code} # Restore the original exit code by returning it.
}

Expand Down
55 changes: 36 additions & 19 deletions src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ impl<'a> Interface<'a> {
self.selection = self.matches.len() - 1;
}

let mut line_offset = 0;
for (index, command) in self.matches.iter().enumerate() {
let mut fg = if self.settings.lightmode {
color::Fg(color::Black).to_string()
Expand Down Expand Up @@ -232,33 +233,39 @@ impl<'a> Interface<'a> {

write!(screen, "{}{}", fg, bg).unwrap();

let command_line_index = self.command_line_index(index as i16);

write!(
screen,
"{}{}",
cursor::Goto(
1,
(command_line_index as i16 + result_top_index as i16) as u16
),
Interface::truncate_for_display(
command,
&self.input.command,
width,
highlight,
fg,
self.debug
let command_display = Interface::truncate_for_display(
command,
&self.input.command,
width,
highlight,
fg,
self.debug,
);

let lines:Vec<&str> = command_display.lines().collect();
let mut lines_count = 0;

for line in self.reverse_if_bottom(lines.iter()) {
write!(
screen,
"{}{}",
cursor::Goto(
1,
(self.command_line_index(line_offset as i16 + lines_count as i16) + result_top_index as i16) as u16
),
line
)
)
.unwrap();
.unwrap();
lines_count += 1;
}

if command.last_run.is_some() {
write!(
screen,
"{}",
cursor::Goto(
width - 9,
(command_line_index as i16 + result_top_index as i16) as u16
(self.command_line_index(line_offset as i16) + result_top_index as i16) as u16
)
)
.unwrap();
Expand Down Expand Up @@ -303,6 +310,8 @@ impl<'a> Interface<'a> {

write!(screen, "{}", color::Bg(color::Reset)).unwrap();
write!(screen, "{}", color::Fg(color::Reset)).unwrap();

line_offset += lines_count;
}
screen.flush().unwrap();
}
Expand Down Expand Up @@ -725,6 +734,14 @@ impl<'a> Interface<'a> {
index
}

fn reverse_if_bottom<I: std::iter::DoubleEndedIterator>(&self, iter: I) -> itertools::Either<I, std::iter::Rev<I>> {
if self.is_screen_view_bottom() {
itertools::Either::Right(iter.rev())
} else {
itertools::Either::Left(iter)
}
}

fn is_screen_view_bottom(&self) -> bool {
self.settings.interface_view == InterfaceView::Bottom
}
Expand Down
10 changes: 10 additions & 0 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ impl Settings {
.value_name("PATH")
.help("The previous directory the user was in before running the command (default $OLDPWD)")
.takes_value(true))
.arg(Arg::with_name("command_from_stdin")
.long("command-from-stdin")
.help("Read the command from `history` (must have HISTTIMEFORMAT=\"%s:\") command piped in.")
.conflicts_with("command"))
.arg(Arg::with_name("command")
.help("The command that was run (default last line of $MCFLY_HISTORY file)")
.value_name("COMMAND")
Expand Down Expand Up @@ -349,6 +353,7 @@ impl Settings {
if let Some(dir) = add_matches.value_of("directory") {
settings.dir = dir.to_string();
} else {
// XXX: Why not just use std::env::current_dir here?
settings.dir = env::var("PWD").unwrap_or_else(|err| {
panic!(
"McFly error: Unable to determine current directory ({})",
Expand All @@ -365,6 +370,11 @@ impl Settings {

if let Some(commands) = add_matches.values_of("command") {
settings.command = commands.collect::<Vec<_>>().join(" ");
} else if add_matches.is_present("command_from_stdin") {
let bash_history =
shell_history::read_from_bash_stdin().expect("Could not read from stdin");
//ignore the other values for now
settings.command = bash_history.command;
} else {
settings.command = shell_history::last_history_line(
&settings.mcfly_history,
Expand Down
36 changes: 36 additions & 0 deletions src/shell_history.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::settings::HistoryFormat;
use regex::Regex;
use regex::RegexBuilder;
use std::env;
use std::fmt;
use std::fs;
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::io::Read;
use std::io::Write;
use std::path::Path;
Expand Down Expand Up @@ -222,6 +224,40 @@ pub fn append_history_entry(command: &HistoryCommand, path: &Path, debug: bool)
}
}

pub struct BashHistoryLine {
pub idx: u32,
pub edited: bool,
pub timestamp: u64,
pub command: String,
}

pub fn read_from_bash_stdin() -> io::Result<BashHistoryLine> {
// See https://github.com/bminor/bash/blob/ce23728687ce9e584333367075c9deef413553fa/builtins/history.def#L386
// First is the history index left-padded with space
// then either ' ' or '*' depending if the entry has been edited
// then a space ''
// then the timestamp in HISTTIMEFORMAT, which should be "%s:"
// then the command
// then a newline
let bash_history_regex =
RegexBuilder::new(r"^\s*(?P<idx>\d+)(?P<edit>[\* ]) (?P<timestamp>\d+):(?P<command>.*)\n$")
.case_insensitive(false)
.dot_matches_new_line(true)
.build()
.unwrap();
let mut full = String::new();
io::stdin().read_to_string(&mut full)?;
let matches = bash_history_regex
.captures(&full)
.expect("History line did not match expected format");
Ok(BashHistoryLine {
idx: matches.name("idx").unwrap().as_str().parse().unwrap(),
edited: matches.name("edit").unwrap().as_str() == "*",
timestamp: matches.name("timestamp").unwrap().as_str().parse().unwrap(),
command: matches.name("command").unwrap().as_str().to_string(),
})
}

#[cfg(test)]
mod tests {
use super::has_leading_timestamp;
Expand Down