#![allow(dead_code)]
use rand::Rng;
use std::path::PathBuf;
use std::process::Command as ShellCommand;
use std::{env, fs, usize};
#[cfg(windows)]
fn is_root() -> bool {
use is_elevated;
is_elevated::is_elevated()
}
#[cfg(not(windows))]
fn is_root() -> bool {
use sudo;
sudo::check() == sudo::RunningAs::Root
}
use crate::EditorState;
pub fn startup_message() {
#[cfg(feature = "startup")]
let mut messages: Vec<&str> = vec![
"the shut up editor", "the not standard text editor", "it's pronounced \"soo ed\"", "nothing comes for granted with this program", "no, it's not an editor that will sue you", "sued as in editor, not as in law", "want a visual mode? ~runhere vim", "what you get is what you get", "what the frick is a config file", "free software, hell yeah", "put that mouse AWAY", "it's got what plants crave!", "looks like you're writing a letter, would you like help?", "more Easter eggs than there are features", "write a Python script then ~runhere python", #[cfg(feature = "lua")]
"write a Lua script then ~script this", "actually, real programmers use ~butterfly", "want features? edit the source code yourself", "loop { let r = read()?; let e = eval(&r); println!(\"{e}\"); }", #[cfg(feature = "lua")]
"~eval for i, line in ipairs(sued_buffer) do print(line) end", "also try Astrion! it's got sued in it!", "no undo for you", "want theming? edit your terminal config", "there's no pretty colours, just text and more text", "in a world full of vscode, be an ed", "notepad, but with less pad", "yeah let's see sublime text do this", "don't worry, you won't get emacs pinky here", "i bet *your* text editor doesn't even ~runhere", ];
#[cfg(feature = "startup")]
let mut sudo_messages: Vec<&str> = vec![
"now i'm super sued!", "i certainly hope you know what you're doing", "this is concerning for several reasons", "be careful while you're back there", "why the frick do you need root access", "please do not do that", "no, this isn't how you're supposed to play the game", "be grateful this is written in rust", "freeman you fool!", "you're doing it wrong", "you might frick up your system, y'know", "instead of root safeguards, we decided to mock you", "aaaand now you're root, fantastic", "the su in sued doesn't mean what you think it does", ];
#[cfg(feature = "startup")]
if is_root() {
messages.append(&mut sudo_messages);
}
#[cfg(feature = "startup")]
let message = messages[rand::thread_rng().gen_range(0..messages.len())];
let version = if cfg!(debug_assertions) {
match ShellCommand::new("git")
.args(&["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|output| String::from_utf8(output.stdout).ok())
.filter(|hash| !hash.trim().is_empty())
{
Some(hash) => format!("{}-devel (c{})", env!("CARGO_PKG_VERSION"), hash.trim()),
None => format!("{}-devel (no commit hash?)", env!("CARGO_PKG_VERSION")),
}
} else {
env!("CARGO_PKG_VERSION").to_string()
};
let root_warning = if is_root() { " as root" } else { "" };
#[cfg(feature = "startup")]
{
if cfg!(debug_assertions) {
println!("sued{root_warning} v{version} - {message}\nthis is a development build, expect bugs and unexpected behaviour\ntype ~cmds or ~help for commands, otherwise just start typing");
} else {
println!("sued{root_warning} v{version} - {message}\ntype ~cmds or ~help for commands, otherwise just start typing");
}
}
#[cfg(not(feature = "startup"))]
{
if cfg!(debug_assertions) {
println!("sued{root_warning} v{version}\nthis is a development build, expect bugs and unexpected behaviour\ntype ~cmds or ~help for commands, otherwise just start typing");
} else {
println!("sued{root_warning} v{version}\ntype ~cmds or ~help for commands, otherwise just start typing");
}
}
}
pub fn open_file(file_path: &str) -> Result<String, String> {
let path_exists = PathBuf::from(file_path).exists();
let file_exists = fs::read_to_string(file_path);
let is_dir = path_exists
&& match fs::metadata(file_path) {
Ok(metadata) => metadata.is_dir(),
Err(_) => false,
};
match file_exists {
Ok(contents) => {
return Ok(contents);
}
Err(e) => {
if is_dir {
println!("{} is a directory - will open as text", file_path);
let listings: Vec<String> = if let Ok(listings) = fs::read_dir(file_path) {
listings
.map(|f| f.unwrap().path().display().to_string())
.collect()
} else {
return Err(format!("failed to open directory {}", file_path));
};
return Ok(listings.join("\n"));
}
let error_specifier: &str;
match e.kind() {
std::io::ErrorKind::NotFound => {
error_specifier = "not found";
}
std::io::ErrorKind::PermissionDenied => {
error_specifier = "can't be opened";
}
std::io::ErrorKind::InvalidData => {
error_specifier = "is not text";
}
_ => {
error_specifier = "failed to open";
}
}
return Err(format!(
"file {} {}: {}",
file_path,
error_specifier,
e.to_string()
));
}
}
}
fn div_thousand(num: f32, repetitions: usize) -> f32 {
let mut n = num;
for _ in 0..repetitions {
n /= 1000.0;
}
n
}
pub fn get_file_size(state: &EditorState) -> String {
let file_size = state
.buffer
.contents
.iter()
.fold(0, |acc, line| acc + line.len());
const NL: usize = 0; const KB: usize = 10_u32.pow(3) as usize;
const MB: usize = 10_u32.pow(6) as usize;
const GB: usize = 10_u32.pow(9) as usize;
const OL: usize = usize::MAX;
match file_size {
NL..KB => format!("{} bytes", file_size),
KB..MB => format!("{} KB", div_thousand(file_size as f32, 1)),
MB..GB => format!("{} MB", div_thousand(file_size as f32, 2)),
GB..OL => format!("{} GB", div_thousand(file_size as f32, 3)),
_ => "like, a lot of bytes".to_string(), }
}
pub fn check_if_line_in_buffer(
file_buffer: &Vec<String>,
line_number: usize,
) -> Result<(), String> {
if line_number < 1 {
return Err(format!("invalid line {}", line_number));
}
if file_buffer.is_empty() {
return Err("no buffer contents".to_string());
}
if line_number <= file_buffer.len() {
return Ok(());
}
Err(format!("no line {}", line_number))
}
pub fn set_cursor_position(args: Vec<&str>, state: &mut EditorState, overtake: bool) -> String {
match state.buffer.contents.len() {
0 => return "no buffer contents".to_string(),
1 => return "buffer contains only one line".to_string(),
_ => (),
}
if args.len() < 2 {
if overtake {
return format!(
"overtake what? try {}overtake [line], between 1 and {}",
state.prefix,
state.buffer.contents.len()
);
}
return format!(
"point where? try {}point [position], between 1 and {}",
state.prefix,
state.buffer.contents.len() + 1
);
}
let is_relative = args[1].starts_with("+") || args[1].starts_with("-");
let position = match args[1].parse::<isize>() {
Ok(pos) => {
if is_relative {
pos + state.cursor as isize + 1
} else {
pos
}
}
Err(e) => match args[1] {
"start" => 1,
"end" => state.buffer.contents.len() as isize + 1,
_ => return format!("invalid position {}: {}", args[1], e),
},
};
if let Err(msg) = check_if_line_in_buffer(&state.buffer.contents, position as usize) {
if position != state.buffer.contents.len() as isize + 1 {
return msg;
}
}
state.cursor = position as usize - 1;
let message: String;
if position == state.buffer.contents.len() as isize + 1 || position <= 1 {
message = format!("set cursor position to {position}");
} else {
if overtake {
let line_contents = state.buffer.contents[state.cursor].clone();
state.buffer.contents.remove(state.cursor);
state.existing_content = line_contents;
message = format!("overtook line {position} and set cursor position");
} else {
let max_length: usize = state.buffer.contents.len().to_string().len();
let line_number_padded = format!("{:width$}", state.cursor, width = max_length);
let output_line = format!(
"{}│{}",
line_number_padded,
state.buffer.contents[state.cursor - 1]
);
message = format!("set cursor position to {position}\n{output_line}");
}
}
message
}
pub fn split_pattern_replacement(combined_args: &str) -> Vec<&str> {
let mut pattern_replacement = Vec::new();
let mut start = 0;
let mut escaped = false;
for (i, c) in combined_args.char_indices() {
if escaped {
escaped = false;
} else if c == '\\' {
escaped = true;
} else if c == '/' {
pattern_replacement.push(&combined_args[start..i]);
start = i + 1;
}
}
if start <= combined_args.len() {
pattern_replacement.push(&combined_args[start..]);
}
pattern_replacement
}