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

Zack/handle login shells #68

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
18 changes: 17 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,34 @@ pub use structopt::StructOpt;
#[structopt(
about = "a cross-shell customizable powerline-like prompt with icons",
name = "silver",
after_help = "https://github.com/reujab/silver/wiki"
after_help = r#"To use silver, put `eval $(silver init)` or equivalent in your shell's
interactive startup file. The `lprint` and `rprint` subcommands are
used by the code emitted by `silver init`; you should not need to use
them directly.

silver expects a configuration file in $XDG_CONFIG_HOME/silver/config.toml
on Unix, or {FOLDERID_RoamingAppData}/silver/config/config.toml on Windows.
(This path can be overridden with the --config option.)
See <https://github.com/reujab/silver/wiki> for documentation of this file.
"#
)]
pub struct Silver {
/// Path of the configuration file to use.
#[structopt(short, long)]
pub config: Option<String>,
/// Name of your shell (e.g. bash).
#[structopt(short, long)]
pub shell: Option<String>,
#[structopt(subcommand)]
pub cmd: Command,
}

#[derive(StructOpt, Debug)]
pub enum Command {
/// Emit shell code to set up silver.
Init,
/// Print the left side of a prompt.
Lprint,
/// Print the right side of a prompt.
Rprint,
}
1 change: 1 addition & 0 deletions src/init.bash
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ PROMPT_COMMAND=silver_prompt
silver_prompt() {
PS1="$(code=$? jobs=$(jobs -p | wc -l) silver lprint)"
}
export SILVER_SHELL="@SILVER_SHELL@"
export VIRTUAL_ENV_DISABLE_PROMPT=1
3 changes: 2 additions & 1 deletion src/init.ion
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fn PROMPT
env code=$? jobs=$(jobs -p | wc -l) silver lprint
end
export VIRTUAL_ENV_DISABLE_PROMPT = 1 # Doesn't make any sense yet
export SILVER_SHELL = "@SILVER_SHELL@"
export VIRTUAL_ENV_DISABLE_PROMPT = 1
3 changes: 2 additions & 1 deletion src/init.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ function prompt {
Start-Process -Wait -NoNewWindow silver lprint
"$([char]0x1b)[0m"
}
$Env:VIRTUAL_ENV_DISABLE_PROMPT = 1
$env:SILVER_SHELL = "@SILVER_SHELL@"
$env:VIRTUAL_ENV_DISABLE_PROMPT = 1
133 changes: 102 additions & 31 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ mod sh;

use cli::*;
use once_cell::sync::{Lazy, OnceCell};
use std::path::{Path, PathBuf};
use sysinfo::{get_current_pid, ProcessExt, System, SystemExt};
use std::{
env,
path::{Path, PathBuf},
};
use sysinfo::{get_current_pid, ProcessExt, RefreshKind, System, SystemExt};

static CONFIG_PATH: OnceCell<PathBuf> = OnceCell::new();

Expand All @@ -21,6 +24,10 @@ static CONFIG: Lazy<config::Config> = Lazy::new(|| {
.expect("Failed to read config")
});

const INIT_BASH: &str = include_str!("init.bash");
const INIT_PS1: &str = include_str!("init.ps1");
const INIT_ION: &str = include_str!("init.ion");

#[derive(Clone, Debug)]
pub struct Segment {
background: String,
Expand All @@ -38,47 +45,111 @@ impl Default for Segment {
}
}

fn main() {
let sys = System::new_all();
let process = sys.get_process(get_current_pid().unwrap()).unwrap();
let parent = sys.get_process(process.parent().unwrap()).unwrap();
let shell = parent.name().trim();
/// Helper function for trimming a String in place. Derived from
/// https://users.rust-lang.org/t/trim-string-in-place/15809/9
fn replace_with_subslice<F>(this: &mut String, f: F)
where
F: FnOnce(&str) -> &str,
{
let original_len = this.len();
let new_slice: &str = f(this);
let start = (new_slice.as_ptr() as usize).wrapping_sub(this.as_ptr() as usize);
if original_len < start {
this.clear();
return;
}

let len = new_slice.len();
if start != 0 {
this.drain(..start);
}
this.truncate(len);
}

/// Identify the type of shell in use.
fn get_shell(opt: &cli::Silver) -> String {
let mut shell: String = if let Some(ref s) = opt.shell {
// If --shell was given on the command line, use that.
s.clone()
} else if let Ok(s) = env::var("SILVER_SHELL") {
// For backward compatibility with 1.1 and earlier,
// use the value of the SILVER_SHELL environment variable.
s
} else {
// Use the name of the parent process, if we can.
// Minimize the amount of information loaded by sysinfo.
// FIXME: Proper error handling, not all these unwraps.
let mut sys = System::new_with_specifics(RefreshKind::new());
// It'd be nice if either the stdlib or sysinfo exposed
// getppid() directly, but they don't.
let mypid = get_current_pid().unwrap();
sys.refresh_process(mypid);
let parentpid = sys.get_process(mypid).unwrap().parent().unwrap();
sys.refresh_process(parentpid);

sys.get_process(parentpid).unwrap().name().to_string()
};

// For Windows compatibility, lowercase the shell's name
// ("BASH.EXE" is the same as "bash.exe").
shell.make_ascii_lowercase();

// Remove any leading or trailing whitespace from `shell`.
// Remove anything up to and including the last '/' or '\\',
// in case the shell was identified by absolute path.
// Remove a trailing ".exe" if present, for Windows compatibility.
// Remove a leading "-" if present; on Unix this is a flag indicating
// a login shell (see login(1) and sh(1)), not part of the name.
// These are done unconditionally, not just when we are using
// the name of the parent process to identify the shell, because
// the installation instructions for older versions of silver
// said to set SILVER_SHELL to "$0" without trimming anything
// from it.
replace_with_subslice(&mut shell, |s| {
let s = s.trim();
let s = s.rsplit(&['/', '\\'][..]).next().unwrap_or(s);
let s = s.strip_prefix('-').unwrap_or(s);
let s = s.strip_suffix(".exe").unwrap_or(s);
s
});

shell
}

fn main() {
let opt = cli::Silver::from_args();

if let Some(path) = opt.config {
if let Some(ref path) = opt.config {
let path = Path::new(path.as_str()).canonicalize().unwrap();
CONFIG_PATH.set(path).unwrap()
}

let _shell = get_shell(&opt);
let shell = _shell.as_str();

match opt.cmd {
Command::Init => {
print!(
"{}",
match shell {
"bash" => include_str!("init.bash"),
"powershell" | "pwsh" | "powershell.exe" | "pwsh.exe" =>
include_str!("init.ps1"),
"ion" => include_str!("init.ion"),
_ =>
panic!(
"unknown shell: \"{}\". Supported shells: bash, ion, powershell",
shell
),
let script = match shell {
"bash" => INIT_BASH,
"powershell" | "pwsh" => INIT_PS1,
"ion" => INIT_ION,
_ => {
use std::process::exit;
eprintln!("silver: unknown shell: \"{}\".", shell);
eprintln!("silver: supported shells: bash, ion, powershell");
exit(1);
}
.replace(
};
let script = script.replace("@SILVER_SHELL@", shell);
let script = if let Some(path) = CONFIG_PATH.get() {
script.replace(
"silver",
format!(
"silver{}",
if let Some(path) = CONFIG_PATH.get() {
format!(" --config {}", path.display())
} else {
String::new()
}
)
.as_str()
format!("silver --config {}", path.display()).as_str(),
)
)
} else {
script
};
print!("{}", script);
}
Command::Lprint => {
print::prompt(&shell, &CONFIG.left, |_, (_, c, n)| {
Expand Down
21 changes: 11 additions & 10 deletions src/modules/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ use std::path::Path;
use url::Url;

pub fn segment(segment: &mut Segment, args: &[&str]) {
let cwd = std::env::current_dir().unwrap();
for dir in CONFIG.git.ignore_dirs.iter() {
if std::env::current_dir().unwrap()
== Path::new(
&shellexpand::full_with_context_no_errors(dir, dirs::home_dir, |s| {
std::env::var(s).map(Some).unwrap_or_default()
})
.into_owned(),
)
.canonicalize()
.unwrap()
{
let expanded = Path::new(
&shellexpand::full_with_context_no_errors(dir, dirs::home_dir, |s| {
std::env::var(s).map(Some).unwrap_or_default()
})
.into_owned(),
)
.canonicalize()
.unwrap();

if cwd == expanded {
return;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/print.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use crate::{config, modules, sh, Segment};
use std::iter::once;

pub fn prompt<T, U>(shell: &str, args: &Vec<config::Segment>, f: T)
pub fn prompt<T, U>(shell: &str, args: &[config::Segment], f: T)
where
T: Fn(usize, (&Segment, &Segment, &Segment)) -> U,
U: IntoIterator<Item = (String, String, String)>,
{
let v: Vec<_> = once(Segment::default())
.chain(
args.into_iter()
args.iter()
.map(|arg| {
let mut segment = Segment {
background: arg.color.background.to_string(),
Expand Down
4 changes: 2 additions & 2 deletions src/sh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ lazy_static! {
}

fn code(color: &str, prefix: &str, light_prefix: &str) -> Option<String> {
let (color, prefix) = if color.starts_with("light") {
(&color[5..], light_prefix)
let (color, prefix) = if let Some(stripped) = color.strip_prefix("light") {
(stripped, light_prefix)
} else {
(color, prefix)
};
Expand Down