Skip to content

Commit

Permalink
Merge commit 'refs/pull/280/head' of github.com:cantino/mcfly into pr…
Browse files Browse the repository at this point in the history
…-280
  • Loading branch information
cantino committed Dec 3, 2023
2 parents e5ad467 + c8e8bc0 commit 8d23d89
Show file tree
Hide file tree
Showing 9 changed files with 736 additions and 98 deletions.
574 changes: 506 additions & 68 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ serde = { version = "1", features = ["derive"] }
humantime = "2.1"
directories-next = "2.0"
itertools = "0.10"
libc = "0.2"
rand = "0.8"
path-absolutize = "3.0.13"
regex = { version = "1", default-features = false, features = ["perf", "std"] }
relative-path = "1.7"
shellexpand = "2.1"
unicode-segmentation = "1.9"

Expand All @@ -43,6 +42,12 @@ features = ["use-dev-tty"]
version = "4"
features = ["derive"]

[target.'cfg(not(windows))'.dependencies]
libc = "0.2"

[target.'cfg(windows)'.dependencies]
autopilot = {git="https://github.com/autopilot-rs/autopilot-rs"}

[features]
default = ["sqlite-bundled"]
sqlite-bundled = ["rusqlite/bundled"]
115 changes: 115 additions & 0 deletions mcfly.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env pwsh

$null = New-Module mcfly {
# We need PSReadLine for a number of capabilities
if ($null -eq (Get-Module -Name PSReadLine)) {
Write-Host "Installing PSReadLine as McFly dependency"
Install-Module PSReadLine
}

# Get history file and make a dummy file for psreadline (hopefully after it has loaded the real history file to its in memory history)
$env:HISTFILE = $null -eq $env:HISTFILE -or "" -eq $env:HISTFILE ? (Get-PSReadLineOption).HistorySavePath : $env:HISTFILE;
$psreadline_dummy = New-TemporaryFile
Set-PSReadLineOption -HistorySavePath $psreadline_dummy.FullName


$fileExists = Test-Path -path $env:HISTFILE
if (-not $fileExists) {
Write-Host "McFly: ${env:HISTFILE} does not exist or is not readable. Please fix this or set HISTFILE to something else before using McFly.";
return 1;
}

# MCFLY_SESSION_ID is used by McFly internally to keep track of the commands from a particular terminal session.
$MCFLY_SESSION_ID = new-guid
$env:MCFLY_SESSION_ID = $MCFLY_SESSION_ID

$env:MCFLY_HISTORY = New-TemporaryFile
Get-Content $env:HISTFILE | Select-Object -Last 100 | Set-Content $env:MCFLY_HISTORY

<#
.SYNOPSIS
Cmdlet to run McFly
.PARAMETER CommandToComplete
The command to complete
.EXAMPLE
Invoke-McFly -CommandToComplete "cargo bu"
#>
function Invoke-McFly {
Param([string]$CommandToComplete)
$lastExitTmp = $LASTEXITCODE
$tempFile = New-TemporaryFile
Start-Process -FilePath '::MCFLY::' -ArgumentList "search", "$CommandToComplete", -o, "$tempFile" -NoNewWindow -Wait
foreach($line in Get-Content $tempFile) {
$key, $value = $line -split ' ', 2
if ("mode" -eq $key) {
$mode = $value
}
if ("commandline" -eq $key) {
$commandline = $value
}
}
if(-not ($null -eq $commandline)) {
[Microsoft.PowerShell.PSConsoleReadLine]::DeleteLine()
[Microsoft.PowerShell.PSConsoleReadline]::Insert($commandline)
if("run" -eq $mode) {
[Microsoft.PowerShell.PSConsoleReadline]::AcceptLine()
}
}
Remove-Item $tempFile
$LASTEXITCODE = $lastExitTmp
}

<#
.SYNOPSIS
Add a command to McFly's history.
.PARAMETER Command
The string of the command to add to McFly's history
.PARAMETER ExitCode
The exit code of the command to add
.EXAMPLE
Add-CommandToMcFly -Command "cargo build"
#>
function Add-CommandToMcFly {
Param (
[string] $Command,
[int] $ExitCode
)
$ExitCode = $ExitCode ?? 0;
$Command | Out-File -FilePath $env:MCFLY_HISTORY -Append
Start-Process -FilePath '::MCFLY::' -ArgumentList add, --exit, $ExitCode, --append-to-histfile, $env:HISTFILE -NoNewWindow | Write-Host
}

# We need to make sure we call out AddToHistoryHandler right after each command is called
Set-PSReadLineOption -HistorySaveStyle SaveIncrementally

Set-PSReadLineOption -PredictionSource HistoryAndPlugin

Set-PSReadLineOption -AddToHistoryHandler {
Param([string]$Command)
$lastExitTmp = $LASTEXITCODE
$Command = $Command.Trim();
# PSReadLine executes this before the command even runs, so we don't know its exit code - assume 0
Add-CommandToMcFly -Command $Command -ExitCode 0
$LASTEXITCODE = $lastExitTmp
# Tell PSReadLine to save the command to their in-memory history (and also the dummy file)
return $true
}

Set-PSReadLineKeyHandler -Chord "Ctrl+r" -ScriptBlock {
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadline]::GetBufferState([ref]$line, [ref]$cursor)
"#mcfly: $line" | Out-File -FilePath $env:MCFLY_HISTORY -Append
Invoke-McFly -CommandToComplete $line
}

Export-ModuleMember -Function @(
"Invoke-McFly"
"Add-CommandToMcFly"
)
}
1 change: 1 addition & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub enum InitMode {
Bash,
Zsh,
Fish,
Powershell,
}

#[derive(Debug, Clone, Copy, ValueEnum, Default)]
Expand Down
9 changes: 8 additions & 1 deletion src/fake_typer.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#[cfg(not(windows))]
use libc;
use std::convert::TryInto;

// Should we be using https://docs.rs/libc/0.2.44/libc/fn.ioctl.html instead?
#[cfg(not(windows))]
extern "C" {
pub fn ioctl(fd: libc::c_int, request: libc::c_ulong, arg: ...) -> libc::c_int;
}

#[cfg(not(windows))]
#[allow(clippy::useless_conversion)]
pub fn use_tiocsti(string: &str) {
for byte in string.as_bytes() {
Expand All @@ -15,3 +17,8 @@ pub fn use_tiocsti(string: &str) {
}
}
}

#[cfg(windows)]
pub fn use_tiocsti(string: &str) {
autopilot::key::type_string(string, &[], 0.0, 0.0);
}
9 changes: 9 additions & 0 deletions src/init.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::settings::InitMode;
use std::env;

pub struct Init {}

Expand All @@ -14,6 +15,9 @@ impl Init {
InitMode::Fish => {
Init::init_fish();
}
InitMode::Powershell => {
Init::init_pwsh();
}
}
Self {}
}
Expand All @@ -29,4 +33,9 @@ impl Init {
let script = include_str!("../mcfly.fish");
print!("{}", script);
}
pub fn init_pwsh() {
let script = include_str!("../mcfly.ps1")
.replace("::MCFLY::", env::current_exe().unwrap().to_str().unwrap());
print!("{}", script);
}
}
6 changes: 4 additions & 2 deletions src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::settings::{InterfaceView, KeyScheme, ResultFilter};
use crate::settings::{ResultSort, Settings};
use chrono::{Duration, TimeZone, Utc};
use crossterm::event::KeyCode::Char;
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use crossterm::style::{Color, Print, SetBackgroundColor, SetForegroundColor};
use crossterm::terminal::{self, LeaveAlternateScreen};
use crossterm::terminal::{Clear, ClearType, EnterAlternateScreen};
Expand Down Expand Up @@ -448,7 +448,6 @@ impl<'a> Interface<'a> {
screen.flush().unwrap();

loop {
terminal::enable_raw_mode().unwrap();
let event =
read().unwrap_or_else(|e| panic!("McFly error: failed to read input {:?}", &e));
self.debug_cursor(&mut screen);
Expand Down Expand Up @@ -511,6 +510,9 @@ impl<'a> Interface<'a> {
}

fn select_with_emacs_key_scheme(&mut self, event: KeyEvent) -> bool {
if event.kind != KeyEventKind::Press {
return false;
}
match event {
KeyEvent {
code: KeyCode::Enter,
Expand Down
93 changes: 69 additions & 24 deletions src/path_update_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,17 @@
use relative_path::RelativePath;
use std::env;
use std::path::{Path, PathBuf};
use crate::settings::pwd;
use path_absolutize::*;
use std::path::Path;
use unicode_segmentation::UnicodeSegmentation;

pub fn normalize_path(incoming_path: &str) -> String {
let expanded_path = shellexpand::tilde(incoming_path);

let current_dir = env::var("PWD").unwrap_or_else(|err| {
panic!(
"McFly error: Unable to determine current directory ({})",
err
)
});
let current_dir_path = Path::new(&current_dir);

let path_buf = if expanded_path.starts_with('/') {
PathBuf::from("/").join(RelativePath::new(&expanded_path).normalize().to_path(""))
} else {
let to_current_dir = RelativePath::new(&expanded_path).to_path(current_dir_path);
RelativePath::new(to_current_dir.to_str().unwrap())
.normalize()
.to_path("/")
};

path_buf
let expanded_path = shellexpand::tilde(incoming_path).to_string();
println!("{}", expanded_path);
return Path::new(&expanded_path)
.absolutize_from(pwd())
.unwrap()
.to_str()
.unwrap_or_else(|| panic!("McFly error: Path must be a valid UTF8 string"))
.to_string()
.to_string();
}

pub fn parse_mv_command(command: &str) -> Vec<String> {
Expand Down Expand Up @@ -108,13 +93,15 @@ mod tests {
use std::path::PathBuf;

#[test]
#[cfg(not(windows))]
fn normalize_path_works_absolute_paths() {
assert_eq!(normalize_path("/foo/bar/baz"), String::from("/foo/bar/baz"));
assert_eq!(normalize_path("/"), String::from("/"));
assert_eq!(normalize_path("////"), String::from("/"));
}

#[test]
#[cfg(not(windows))]
fn normalize_path_works_with_tilda() {
assert_eq!(normalize_path("~/"), env::var("HOME").unwrap());
assert_eq!(
Expand All @@ -126,6 +113,7 @@ mod tests {
}

#[test]
#[cfg(not(windows))]
fn normalize_path_works_with_double_dots() {
assert_eq!(normalize_path("/foo/bar/../baz"), String::from("/foo/baz"));
assert_eq!(normalize_path("/foo/bar/../../baz"), String::from("/baz"));
Expand All @@ -140,6 +128,63 @@ mod tests {
assert_eq!(normalize_path("~/foo/bar/../.."), env::var("HOME").unwrap());
}

#[cfg(windows)]
fn windows_home_path() -> String {
PathBuf::from(env::var("HOMEDRIVE").unwrap())
.join(env::var("HOMEPATH").unwrap())
.to_str()
.unwrap()
.to_string()
}

#[test]
#[cfg(windows)]
fn normalize_path_works_absolute_paths() {
assert_eq!(
normalize_path("C:\\foo\\bar\\baz"),
String::from("C:\\foo\\bar\\baz")
);
assert_eq!(normalize_path("C:\\"), String::from("C:\\"));
assert_eq!(normalize_path("C:\\\\\\\\"), String::from("C:\\"));
}

#[test]
#[cfg(windows)]
fn normalize_path_works_with_tilda() {
assert_eq!(normalize_path("~\\"), windows_home_path());
assert_eq!(
normalize_path("~\\foo"),
PathBuf::from(windows_home_path())
.join("foo")
.to_string_lossy()
);
}

#[test]
#[cfg(windows)]
fn normalize_path_works_with_double_dots() {
assert_eq!(
normalize_path("C:\\foo\\bar\\..\\baz"),
String::from("C:\\foo\\baz")
);
assert_eq!(
normalize_path("C:\\foo\\bar\\..\\..\\baz"),
String::from("C:\\baz")
);
assert_eq!(
normalize_path("C:\\foo\\bar\\..\\..\\"),
String::from("C:\\")
);
assert_eq!(normalize_path("C:\\foo\\bar\\..\\.."), String::from("C:\\"));
assert_eq!(
normalize_path("~\\foo\\bar\\..\\baz"),
PathBuf::from(windows_home_path())
.join("foo\\baz")
.to_string_lossy()
);
assert_eq!(normalize_path("~\\foo\\bar\\..\\.."), windows_home_path());
}

#[test]
fn parse_mv_command_works_in_the_basic_case() {
assert_eq!(
Expand Down
Loading

0 comments on commit 8d23d89

Please sign in to comment.