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

Add Windows and Powershell support #280

Merged
merged 37 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
46a7a87
replace termion with crossterm for portability & configurability
dmfay Jun 1, 2021
60490b9
supply alternate default colors for MCFLY_LIGHT
dmfay Jun 5, 2021
6b89f60
cargo update
dmfay Jun 21, 2021
14dff30
wip
jtschuster Aug 24, 2022
394396e
Somewhat working on windows????
jtschuster Aug 26, 2022
e61b21c
Working on windows reliably
jtschuster Aug 28, 2022
74ac425
wip
jtschuster Sep 1, 2022
4e2f83d
Merge branch 'master' of https://github.com/cantino/mcfly into mergeMain
jtschuster Sep 1, 2022
5dc17fe
wip
jtschuster Sep 1, 2022
9a6afba
Add windows path test and update path normalizer
jtschuster Sep 2, 2022
24ed41f
Clean up
jtschuster Sep 4, 2022
44e79e7
clean a little more
jtschuster Sep 4, 2022
2ff4ff2
VSCode files gitignore
jtschuster Sep 4, 2022
d7f9ff2
Final clean
jtschuster Sep 4, 2022
a74cadd
Couple other small changes
jtschuster Sep 4, 2022
0033d7c
format
jtschuster Sep 4, 2022
8813ea7
Merge branch 'master' of https://github.com/cantino/mcfly into crosst…
jtschuster Nov 5, 2022
3fc6a15
Avoid the black flicker on key strokes by never clearing all
jtschuster Nov 5, 2022
154d639
format
jtschuster Nov 5, 2022
5c978dd
Add loop to clear lines where there are no longer matches
jtschuster Nov 6, 2022
0eeb04c
Format
jtschuster Nov 6, 2022
9e08024
Merge branch 'crossterm' into crossterm-and-colors
jtschuster Feb 26, 2023
558b147
pwsh
jtschuster Feb 26, 2023
f9e802d
Clear every results line
jtschuster Feb 27, 2023
c7bf093
Fix bug in autopilot crate
jtschuster Feb 28, 2023
7ed4079
Fix clippy warnings
jtschuster Feb 28, 2023
95f0375
Merge branch 'master' of https://github.com/cantino/mcfly into crosst…
jtschuster Mar 5, 2023
a020f68
fmt
jtschuster Mar 5, 2023
f074c93
make autopilot a dependency on windows only
jtschuster Mar 6, 2023
f396487
Merge branch 'master' of https://github.com/cantino/mcfly into crosst…
jtschuster Mar 6, 2023
0a3013f
Merge main
jtschuster Sep 11, 2023
6d97752
remerge
jtschuster Sep 11, 2023
2671961
Add command to temp file by default instead of using autopilot
jtschuster Sep 12, 2023
1f17260
Always load mcfly, add blank line to mcfly history before call
jtschuster Sep 12, 2023
db36db4
Remove Dirs dep, fix non-windows warning, add unwrap_or
jtschuster Sep 12, 2023
a28ab76
Remove relative-path
jtschuster Sep 12, 2023
c8e8bc0
Use PWD function instead of env::curent_dir in normalize path
jtschuster Sep 12, 2023
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
908 changes: 631 additions & 277 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ csv = "1"
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 @@ -36,11 +35,16 @@ features = ["functions", "unlock_notify"]
version = "0.26"
features = ["use-dev-tty"]


[dependencies.clap]
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 @@ -124,6 +124,7 @@ pub enum InitMode {
Bash,
Zsh,
Fish,
Powershell,
}

impl Cli {
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
Loading