/* relish: versatile lisp shell * Copyright (C) 2021 Aidan Hahn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ use nu_ansi_term::{Color, Style}; use dirs::home_dir; use relish::ast::{eval, lex, Ctr, Seg, SymTable}; use relish::aux::configure; use relish::stdlib::{dynamic_stdlib, static_stdlib}; use reedline::{ FileBackedHistory, DefaultHinter, DefaultValidator, Reedline, Signal, Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, }; use std::borrow::Cow; use std::env; #[derive(Clone)] pub struct CustomPrompt<'a>(&'a str); impl Prompt for CustomPrompt<'_> { fn render_prompt_left(&self) -> Cow { { Cow::Owned(self.0.to_string()) } } fn render_prompt_right(&self) -> Cow { { Cow::Owned(format!(" <")) } } fn render_prompt_indicator(&self, _edit_mode: PromptEditMode) -> Cow { Cow::Owned("> ".to_string()) } fn render_prompt_multiline_indicator(&self) -> Cow { Cow::Borrowed("++++") } fn render_prompt_history_search_indicator( &self, history_search: PromptHistorySearch, ) -> Cow { let prefix = match history_search.status { PromptHistorySearchStatus::Passing => "", PromptHistorySearchStatus::Failing => "failing ", }; Cow::Owned(format!( "({}reverse-search: {}) ", prefix, history_search.term )) } } fn main() -> ! { const HIST_FILE: &str = "/.relish_hist"; const CONFIG_FILE_DEFAULT: &str = "/.relishrc"; let home_dir = home_dir().unwrap().to_str().unwrap().to_owned(); let hist_file_name = home_dir.clone() + HIST_FILE; let cfg_file_name = home_dir + CONFIG_FILE_DEFAULT; let mut rl = Reedline::create(); let maybe_hist: Box; if !hist_file_name.is_empty() { maybe_hist = Box::new(FileBackedHistory::with_file(5, hist_file_name.into()) .expect("error reading history!")); rl = rl.with_history(maybe_hist); } rl = rl.with_hinter(Box::new( DefaultHinter::default() .with_style(Style::new().italic().fg(Color::LightGray)), )).with_validator(Box::new(DefaultValidator)); let mut syms = SymTable::new(); static_stdlib(&mut syms).unwrap_or_else(|err: String| eprintln!("{}", err)); dynamic_stdlib(&mut syms).unwrap_or_else(|err: String| eprintln!("{}", err)); { // scope the below borrow of syms let cfg_file = env::var("RELISH_CFG_FILE").unwrap_or(cfg_file_name); configure(cfg_file.clone(), &mut syms) .unwrap_or_else(|err: String| eprintln!("failed to load script {}\n{}", cfg_file, err)); } dynamic_stdlib(&mut syms).unwrap_or_else(|err: String| eprintln!("{}", err)); loop { let s = *syms .call_symbol(&"CFG_RELISH_PROMPT".to_string(), &Seg::new(), true) .unwrap_or_else(|err: String| { eprintln!("{}", err); Box::new(Ctr::String("".to_string())) }); let p_str = s.to_string(); let readline_prompt = CustomPrompt(p_str.as_str()); let user_doc = rl.read_line(&readline_prompt).unwrap(); match user_doc { Signal::Success(line) => { let l = line.as_str().to_owned(); match lex(&l) { Ok(a) => match eval(&a, &mut syms) { Ok(a) => println!("{}", a), Err(s) => println!("{}", s), }, Err(s) => println!("{}", s), } }, Signal::CtrlD => { println!("EOF!"); panic!(); }, Signal::CtrlC => { println!("Interrupted!"); }, } } }