diff --git a/.codecov.yml b/.codecov.yml index 47f6f748..38d8d949 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -3,11 +3,11 @@ coverage: patch: default: target: 1% - threshold: 1% + threshold: 90% path: "src" project: default: - target: 50% - threshold: 50% + target: 1% + threshold: 90% path: "src" diff --git a/CHANGELOG.md b/CHANGELOG.md index d1f29fa6..daac6a5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## v1.2.1 +- F# support +- Fix multiline display in floating windows +- Deno brings REPL support for Javascript and TypeScript + ## v1.2 - Live mode (a @ChristianChiarulli idea and partial realisation) - Lower ressources usage for REPL interpreters diff --git a/Cargo.lock b/Cargo.lock index e22ec2cc..b27ebae0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" @@ -336,7 +336,7 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "sniprun" -version = "1.1.2" +version = "1.2.1" dependencies = [ "dirs", "libc", diff --git a/Cargo.toml b/Cargo.toml index f69bcd0a..5bbe397b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sniprun" -version = "1.2.0" +version = "1.2.1" authors = ["michaelb "] edition = "2018" diff --git a/README.md b/README.md index 3028da05..6e25141f 100755 --- a/README.md +++ b/README.md @@ -431,10 +431,11 @@ println!("-> {}", alphabet); | C++ | Import | No | | Coffeescript | Bloc | No | | D | Bloc | No | +| F# | Bloc | No, but _could_ \*\* | | Go | Bloc | No | | Haskell | Line | No | | Java | Bloc | No | -| JavaScript | Bloc | No | +| JavaScript | Bloc | Yes\*\* (Deno)| | Julia | Bloc | Yes\*\* | | Lua | Bloc | No | | Lua-nvim | Bloc | Yes\*\* | @@ -448,8 +449,7 @@ println!("-> {}", alphabet); | Rust | Bloc | No | | SageMath | Import | Yes\*\* | | Scala | Bloc | No | -| TypeScript | Bloc | No | - +| TypeScript | Bloc | Yes\*\* (Deno)| Want support for your language? Submit an [issue](https://github.com/michaelb/sniprun/issues/new?assignees=&labels=new-langage-support&template=support-for--language-.md&title=), or even better, [contribute](CONTRIBUTING.md), it's easy! diff --git a/doc/FSharp_Original.md b/doc/FSharp_Original.md new file mode 100644 index 00000000..37592088 --- /dev/null +++ b/doc/FSharp_Original.md @@ -0,0 +1,61 @@ +# This interpreter relies on dotnet fsi being available and on your path + + +The default interpreter command is `dotnet fsi --nologo` but it can be changed via the configuration key + + +``` +require'sniprun'.setup({ + interpreter_options = { + FSharp_fifo = { + interpreter = "...." + } + } + } +}) +``` + + +### REPL (would solve slowness issues) + +For now, REPL is broken due to dotnet fsi being capricious about its stdin. + +I'll explain rapidly how sniprun implement a REPL interpreter around named pipes (FIFOs). + +The first time a fifo-based interpreter receive a run command, it forks to the background and executes `ressources/init_repl.sh`. +There is a lot of thing in that script but to replicate, you just have to: + + + +- `mkfifo pipe_in` + +- create a launcher script: + +```bash +#!/bin/bash +/bin/cat pipe_in | dotnet fsi + +# or replace 'dotnet fsi' by whatever you cant to try +``` + +- launch it in the background: `bash ./launcher.sh &`, (or `bash ./launcher.sh > out.txt & ` to redirect stdout to out.txt like sniprun does) + +- ensure the pipe will stay open: `sleep 3600 > pipe_in &` (cat, exec 3> variations will also work) + +- `echo "printfn \" hey \" " > pipe_in` or `cat hello_world.fsx > pipe_in` + +- normally, the result should be printed in the terminal that ran the launcher, or in the out file. + + + + +#### The issue: + +right now, dotnet fsi looks like it's blocked by the first sleep > pipe_in... but something **has** to keep the pipe open or when it closes, the fsi REPL reading from that will exit. + +I suspect the thing has something to do with interactive mode. + +For example, `python` has a similar problem, but `python -i ` (forced interactive mode, even if no terminal is detected because it runs in the background / its stdin was hijacked) works fine in the above example. + +If you find something to replace dotnet fsi with, that exhibits the same correct behavior as `python -i`, sniprun REPL mode _should_ work. + diff --git a/ressources/install_all_compilers_ci.sh b/ressources/install_all_compilers_ci.sh index 07fd419c..9c080052 100755 --- a/ressources/install_all_compilers_ci.sh +++ b/ressources/install_all_compilers_ci.sh @@ -6,6 +6,7 @@ sudo apt install haskell-platform -y sudo apt install -y nodejs npm sudo npm install -g coffee-script sudo npm install -g typescript +sudo npm install -g ts-node sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9 sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu focal-cran40/' sudo apt install r-base @@ -15,5 +16,11 @@ pip3 install jupyter sudo apt install lua5.3 sudo apt install sagemath sudo apt install gprolog +sudo apt install dotnet ./ressources/go_install.sh export PATH=$PATH:$HOME/golang/go/bin/ + +# deno for typescript and javascript +# cargo install deno --locked # too long, takes 20 min! +curl -fsSL https://deno.land/x/install/install.sh | sh +cp $HOME/.deno/bin/* $HOME/.cargo/bin diff --git a/src/display.rs b/src/display.rs index edd1dd91..1be21209 100644 --- a/src/display.rs +++ b/src/display.rs @@ -307,16 +307,18 @@ pub fn display_floating_window( "lua require\"sniprun.display\".fw_open({},{},\"{}\", true)", row - 1, col, - no_output_wrap(result, data, &DisplayType::TempFloatingWindow), + no_output_wrap(&result.to_string(), data, &DisplayType::TempFloatingWindow) + .replace("\n", "\\\n"), )), Err(result) => nvim.lock().unwrap().command(&format!( "lua require\"sniprun.display\".fw_open({},{},\"{}\", false)", row - 1, col, - no_output_wrap(&result.to_string(), data, &DisplayType::TempFloatingWindow), + no_output_wrap(&result.to_string(), data, &DisplayType::TempFloatingWindow) + .replace("\n", "\\\n"), )), }; - info!("disaply floating window res = {:?}", res); + info!("display floating window res = {:?}", res); } pub fn return_message_classic( diff --git a/src/interpreters/FSharp_fifo.rs b/src/interpreters/FSharp_fifo.rs new file mode 100644 index 00000000..135c0a82 --- /dev/null +++ b/src/interpreters/FSharp_fifo.rs @@ -0,0 +1,358 @@ +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct FSharp_fifo { + support_level: SupportLevel, + data: DataHolder, + code: String, + main_file_path: String, + plugin_root: String, + cache_dir: String, + + interpreter: String, + current_output_id: u32, +} + +impl FSharp_fifo { + fn wait_out_file( + &self, + out_path: String, + err_path: String, + id: u32, + ) -> Result { + let end_mark = String::from("sniprun_finished_id=") + &id.to_string() + "\n"; + let start_mark = String::from("sniprun_started_id=") + &id.to_string(); + + info!( + "searching for things between {:?} and {:?}", + start_mark, end_mark + ); + + let mut out_contents = String::new(); + let mut err_contents = String::new(); + + let mut pause = std::time::Duration::from_millis(50); + let start = std::time::Instant::now(); + loop { + std::thread::sleep(pause); + pause = pause.saturating_add(std::time::Duration::from_millis(50)); + + // timeout after 30s if no result found + if start.elapsed().as_secs() > 30 { + return Err(SniprunError::InterpreterLimitationError(String::from( + "reached the 30s timeout", + ))); + } + + //check for stderr first + if let Ok(mut file) = std::fs::File::open(&err_path) { + info!("errfile exists"); + out_contents.clear(); + let res = file.read_to_string(&mut err_contents); + if res.is_ok() { + info!("errfile could be read : {:?}", err_contents); + // info!("file : {:?}", contents); + if err_contents.contains(&end_mark) { + if let Some(index) = err_contents.rfind(&start_mark) { + let mut err_to_display = err_contents + [index + start_mark.len()..err_contents.len() - end_mark.len() - 1] + .to_owned(); + info!("err to display : {:?}", err_to_display); + if !err_to_display.trim().is_empty() { + info!("err found"); + if err_to_display.lines().count() > 2 { + let mut err_to_display_vec = + err_to_display.lines().skip(2).collect::>(); + err_to_display_vec.dedup(); + err_to_display = err_to_display_vec.join("\n"); + } + + return Err(SniprunError::RuntimeError(err_to_display)); + } + } + } + } + } + + //check for stdout + if let Ok(mut file) = std::fs::File::open(&out_path) { + info!("file exists"); + out_contents.clear(); + let res = file.read_to_string(&mut out_contents); + if res.is_ok() { + info!("file could be read : {:?}", out_contents); + // info!("file : {:?}", contents); + if out_contents.contains(&end_mark) { + info!("out found"); + let index = out_contents.rfind(&start_mark).unwrap(); + return Ok(out_contents + [index + start_mark.len()..out_contents.len() - end_mark.len() - 1] + .to_owned()); + } + } + } + + info!("not found yet"); + } + } + + fn get_nvim_pid(data: &DataHolder) -> String { + data.nvim_pid.to_string() + } + + fn fetch_config(&mut self) { + let default_interpreter = String::from("dotnet fsi --nologo"); + self.interpreter = default_interpreter; + if let Some(used_interpreter) = + FSharp_fifo::get_interpreter_option(&self.get_data(), "interpreter") + { + if let Some(interpreter_string) = used_interpreter.as_str() { + info!("Using custom interpreter: {}", interpreter_string); + self.interpreter = interpreter_string.to_string(); + } + } + } +} + +impl Interpreter for FSharp_fifo { + fn new_with_level(data: DataHolder, level: SupportLevel) -> Box { + //create a subfolder in the cache folder + let rwd = data.work_dir.clone() + "/fsharp_fifo"; + let mut builder = DirBuilder::new(); + builder.recursive(true); + builder + .create(&rwd) + .expect("Could not create directory for fsharp-fifo"); + + //pre-create string pointing to main file's and binary's path + let mfp = rwd.clone() + "/main.fsx"; + + let pgr = data.sniprun_root_dir.clone(); + Box::new(FSharp_fifo { + cache_dir: rwd + "/" + &FSharp_fifo::get_nvim_pid(&data), + data, + support_level: level, + code: String::from(""), + main_file_path: mfp, + plugin_root: pgr, + current_output_id: 0, + interpreter: String::new(), + }) + } + + fn get_name() -> String { + String::from("FSharp_fifo") + } + + fn default_for_filetype() -> bool { + false + } + + fn behave_repl_like_default() -> bool { + false + } + + fn has_repl_capability() -> bool { + true + } + + fn get_supported_languages() -> Vec { + vec![String::from("F#"), String::from("fsx"), String::from("fs")] + } + + fn get_current_level(&self) -> SupportLevel { + self.support_level + } + fn set_current_level(&mut self, level: SupportLevel) { + self.support_level = level; + } + + fn get_data(&self) -> DataHolder { + self.data.clone() + } + + fn get_max_support_level() -> SupportLevel { + SupportLevel::Bloc + } + + fn fetch_code(&mut self) -> Result<(), SniprunError> { + self.fetch_config(); + if !self + .data + .current_bloc + .replace(&[' ', '\t', '\n', '\r'][..], "") + .is_empty() + && self.get_current_level() >= SupportLevel::Bloc + { + self.code = self.data.current_bloc.clone(); + } else if !self.data.current_line.replace(" ", "").is_empty() + && self.get_current_level() >= SupportLevel::Line + { + self.code = self.data.current_line.clone(); + } else { + self.code = String::from(""); + } + + Ok(()) + } + fn add_boilerplate(&mut self) -> Result<(), SniprunError> { + Ok(()) + } + fn build(&mut self) -> Result<(), SniprunError> { + write(&self.main_file_path, &self.code).expect("Unable to write to file for fsharp_fifo"); + Ok(()) + } + fn execute(&mut self) -> Result { + let output = Command::new(self.interpreter.split_whitespace().next().unwrap()) + .args(self.interpreter.split_whitespace().skip(1)) + .arg(&self.interpreter) + .arg(&self.main_file_path) + .args(&self.get_data().cli_args) + .output() + .expect("Unable to start process"); + if output.status.success() { + Ok(String::from_utf8(output.stdout).unwrap()) + } else { + return Err(SniprunError::RuntimeError( + String::from_utf8(output.stderr.clone()) + .unwrap() + .lines() + .last() + .unwrap_or(&String::from_utf8(output.stderr).unwrap()) + .to_owned(), + )); + } + } +} + +impl ReplLikeInterpreter for FSharp_fifo { + fn fetch_code_repl(&mut self) -> Result<(), SniprunError> { + if !self.read_previous_code().is_empty() { + // nothing to do, kernel already running + info!("fsi kernel already running"); + + if let Some(id) = self.get_pid() { + // there is a race condition here but honestly you'd have to + // trigger it on purpose + self.current_output_id = id + 1; + self.set_pid(self.current_output_id); + } else { + info!("Could not retrieve a previous id even if the kernel is running"); + info!("This was in saved code: {}", self.read_previous_code()); + return Err(SniprunError::CustomError( + "Sniprun failed to connect to the running kernel, please SnipReset".to_string(), + )); + } + + self.fetch_code()?; + Ok(()) + } else { + self.fetch_config(); + // launch everything + self.set_pid(0); + + let init_repl_cmd = self.data.sniprun_root_dir.clone() + "/ressources/init_repl.sh"; + info!( + "launching kernel : {:?} on {:?}", + init_repl_cmd, &self.cache_dir + ); + + match daemon() { + Ok(Fork::Child) => { + let _res = Command::new("bash") + .args(&[ + init_repl_cmd, + self.cache_dir.clone(), + self.interpreter.clone(), + ]) + .output() + .unwrap(); + let pause = std::time::Duration::from_millis(36_000_000); + std::thread::sleep(pause); + + return Err(SniprunError::CustomError( + "Timeout expired for dotnet fsi REPL".to_owned(), + )); + } + Ok(Fork::Parent(_)) => {} + Err(_) => info!( + "FSharp_fifo could not fork itself to the background to launch the kernel" + ), + }; + + self.save_code("kernel_launched\n".to_owned()); + + let pause = std::time::Duration::from_millis(2000); // prevent an user from re-running the snippet + // before dotnet launches (2-3 secs) + std::thread::sleep(pause); + Err(SniprunError::CustomError( + "F# interactive kernel launched, re-run your snippet".to_owned(), + )) + } + } + + fn add_boilerplate_repl(&mut self) -> Result<(), SniprunError> { + self.add_boilerplate()?; + let start_mark = String::from("\nprintfn \"sniprun_started_id=") + + &self.current_output_id.to_string() + + "\"\n"; + let end_mark = String::from("\nprintfn \"sniprun_finished_id=") + + &self.current_output_id.to_string() + + "\"\n"; + let start_mark_err = String::from("\neprintfn \"sniprun_started_id=") + + &self.current_output_id.to_string() + + "\" \n"; + let end_mark_err = String::from("\neprintfn \"sniprun_finished_id=") + + &self.current_output_id.to_string() + + "\"\n"; + + // remove empty lines interpreted as 'enter' by the repl + self.code = self + .code + .lines() + .filter(|l| !l.trim().is_empty()) + .collect::>() + .join("\n"); + + let all_code = String::from("\n") + &self.code + "\n\n"; + self.code = start_mark + &start_mark_err + &all_code + &end_mark + &end_mark_err; + Ok(()) + } + + fn build_repl(&mut self) -> Result<(), SniprunError> { + self.build() + } + + fn execute_repl(&mut self) -> Result { + + let send_repl_cmd = self.data.sniprun_root_dir.clone() + "/ressources/launcher_repl.sh"; + info!("running launcher {}", send_repl_cmd); + let res = Command::new(send_repl_cmd) + .arg(self.main_file_path.clone()) + .arg(self.cache_dir.clone() + "/fifo_repl/pipe_in") + .spawn(); + info!("cmd status: {:?}", res); + res.expect("could not run launcher"); + // info!("launcher launched : {:?}", res); + + let outfile = self.cache_dir.clone() + "/fifo_repl/out_file"; + let errfile = self.cache_dir.clone() + "/fifo_repl/err_file"; + info!("outfile : {:?}", outfile); + self.wait_out_file(outfile, errfile, self.current_output_id) + } +} + +#[cfg(test)] +mod test_fsharp_fifo { + use super::*; + + #[test] + fn simple_print() { + let mut data = DataHolder::new(); + data.current_bloc = String::from("printfn \"lol\""); + let mut interpreter = FSharp_fifo::new(data); + let res = interpreter.run_at_level(SupportLevel::Bloc); + // should panic if not an Ok() + let string_result = res.unwrap(); + assert_eq!(string_result, "lol\n"); + } +} diff --git a/src/interpreters/JS_TS_deno.rs b/src/interpreters/JS_TS_deno.rs new file mode 100644 index 00000000..601dd15a --- /dev/null +++ b/src/interpreters/JS_TS_deno.rs @@ -0,0 +1,384 @@ +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct JS_TS_deno { + support_level: SupportLevel, + data: DataHolder, + code: String, + cache_dir: String, + + current_output_id: u32, + language_work_dir: String, + main_file_path: String, +} + +impl JS_TS_deno { + fn wait_out_file( + &self, + out_path: String, + err_path: String, + id: u32, + ) -> Result { + let end_mark = String::from("sniprun_finished_id=") + &id.to_string(); + let start_mark = String::from("sniprun_started_id=") + &id.to_string(); + + info!( + "searching for things between {:?} and {:?}", + start_mark, end_mark + ); + + let mut out_contents = String::new(); + let mut err_contents = String::new(); + + let mut pause = std::time::Duration::from_millis(50); + let start = std::time::Instant::now(); + loop { + std::thread::sleep(pause); + pause = pause.saturating_add(std::time::Duration::from_millis(50)); + + // timeout after 30s if no result found + if start.elapsed().as_secs() > 30 { + return Err(SniprunError::InterpreterLimitationError(String::from( + "reached the 30s timeout", + ))); + } + + //check for stderr first + if let Ok(mut file) = std::fs::File::open(&err_path) { + info!("errfile exists"); + out_contents.clear(); + let res = file.read_to_string(&mut err_contents); + if res.is_ok() { + // info!("errfile could be read : {:?}", err_contents); + if err_contents.contains(&end_mark) { + if let Some(index) = err_contents.rfind(&start_mark) { + let mut err_to_display = err_contents + [index + start_mark.len()..err_contents.len() - end_mark.len() - 1] + .to_owned(); + info!("err to display : {:?}", err_to_display); + if !err_to_display.trim().is_empty() { + info!("err found"); + if err_to_display.lines().count() > 2 { + let mut err_to_display_vec = + err_to_display.lines().skip(2).collect::>(); + err_to_display_vec.dedup(); + err_to_display = err_to_display_vec.join("\n"); + } + + return Err(SniprunError::RuntimeError(err_to_display)); + } + } + } + } + } + + //check for stdout + if let Ok(mut file) = std::fs::File::open(&out_path) { + info!("file exists"); + out_contents.clear(); + let res = file.read_to_string(&mut out_contents); + if res.is_ok() { + // info!("out {}", out_contents); + let relevant_content: String = out_contents + .lines() + .filter(|l| !l.contains("undefined")) + .collect::>() + .join("\n"); + info!("relevant {}", relevant_content); + info!("file could be read : {:?}", relevant_content); + // info!("file : {:?}", contents); + if relevant_content.contains(&end_mark) { + info!("out found"); + let index = relevant_content.rfind(&start_mark).unwrap(); + return Ok(relevant_content[index + start_mark.len() + ..relevant_content.len() - end_mark.len() - 1] + .to_owned()); + } + } + } + + info!("not found yet"); + } + } +} + +impl Interpreter for JS_TS_deno { + fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box { + //create a subfolder in the cache folder + let lwd = data.work_dir.clone() + "/js-ts_deno"; + let mut builder = DirBuilder::new(); + builder.recursive(true); + builder + .create(&lwd) + .expect("Could not create directory for example"); + + //pre-create string pointing to main file's and binary's path + let mfp = lwd.clone() + "/main.ts"; + Box::new(JS_TS_deno { + cache_dir: lwd.clone() + "/" + &Python3_fifo::get_nvim_pid(&data), + data, + support_level, + code: String::new(), + language_work_dir: lwd, + main_file_path: mfp, + current_output_id: 0, + }) + } + + fn get_supported_languages() -> Vec { + vec![ + String::from("TS/JS via Deno"), // in 1st position of vector, used for info only + //':set ft?' in nvim to get the filetype of opened file + String::from("typescript"), + String::from("typescriptreact"), + String::from("ts"), //should not be necessary, but just in case + String::from("js"), + String::from("javascript"), + ] + } + + fn get_name() -> String { + // get your interpreter name + String::from("JS_TS_deno") + } + + fn get_current_level(&self) -> SupportLevel { + self.support_level + } + fn set_current_level(&mut self, level: SupportLevel) { + self.support_level = level; + } + + fn default_for_filetype() -> bool { + false + } + fn get_data(&self) -> DataHolder { + self.data.clone() + } + + fn get_max_support_level() -> SupportLevel { + //define the max level support of the interpreter (see readme for definitions) + SupportLevel::Bloc + } + + fn behave_repl_like_default() -> bool { + true + } + + fn has_repl_capability() -> bool { + true + } + + fn fetch_code(&mut self) -> Result<(), SniprunError> { + //note: you probably don't have to modify, or even understand this function + + //here if you detect conditions that make higher support level impossible, + //or unecessary, you should set the current level down. Then you will be able to + //ignore maybe-heavy code that won't be needed anyway + + //add code from data to self.code + if !self + .data + .current_bloc + .replace(&[' ', '\t', '\n', '\r'][..], "") + .is_empty() + && self.support_level >= SupportLevel::Bloc + { + self.code = self.data.current_bloc.clone(); + } else if !self.data.current_line.replace(" ", "").is_empty() + && self.support_level >= SupportLevel::Line + { + self.code = self.data.current_line.clone(); + } else { + // no code was retrieved + self.code = String::from(""); + } + + // now self.code contains the line or bloc of code wanted :-) + info!("javascript/typescript self.code) = {}", self.code); + Ok(()) + } + + fn add_boilerplate(&mut self) -> Result<(), SniprunError> { + Ok(()) + } + + fn build(&mut self) -> Result<(), SniprunError> { + //write code to file + let mut _file = + File::create(&self.main_file_path).expect("failed to create file for js_ts_deno"); + // io errors can be ignored, or handled into a proper sniprunerror + // if you panic, it should not be too dangerous for anyone + write(&self.main_file_path, &self.code).expect("unable to write to file for js_ts_deno"); + + Ok(()) + } + + fn execute(&mut self) -> Result { + //run th binary and get the std output (or stderr) + let output = Command::new("deno") + .arg("run") + .arg("-A") + .arg("--unstable") + .arg(&self.main_file_path) + .output() + .expect("Unable to start process"); + + if output.status.success() { + //return stdout + Ok(String::from_utf8(output.stdout).unwrap()) + } else { + // return stderr + Err(SniprunError::RuntimeError( + String::from_utf8(output.stderr).unwrap(), + )) + } + } +} + +impl ReplLikeInterpreter for JS_TS_deno { + fn fetch_code_repl(&mut self) -> Result<(), SniprunError> { + if !self.read_previous_code().is_empty() { + // nothing to do, kernel already running + info!("Deno kernel already running"); + + if let Some(id) = self.get_pid() { + // there is a race condition here but honestly you'd have to + // trigger it on purpose + self.current_output_id = id + 1; + self.set_pid(self.current_output_id); + } else { + info!("Could not retrieve a previous id even if the kernel is running"); + info!("This was in saved code: {}", self.read_previous_code()); + return Err(SniprunError::CustomError( + "Sniprun failed to connect to the running kernel, please SnipReset".to_string(), + )); + } + + self.fetch_code()?; + Ok(()) + } else { + // launch everything + self.set_pid(0); + + let init_repl_cmd = self.data.sniprun_root_dir.clone() + "/ressources/init_repl.sh"; + info!( + "launching kernel : {:?} on {:?}", + init_repl_cmd, &self.cache_dir + ); + + match daemon() { + Ok(Fork::Child) => { + let _res = Command::new("bash") + .args(&[ + init_repl_cmd, + self.cache_dir.clone(), + String::from("deno"), + String::from("repl"), + String::from("-q"), + ]) + .output() + .unwrap(); + let pause = std::time::Duration::from_millis(36_000_000); + std::thread::sleep(pause); + + return Err(SniprunError::CustomError( + "Timeout expired for python3 REPL".to_owned(), + )); + } + Ok(Fork::Parent(_)) => {} + Err(_) => { + info!("JS_TS_deno could not fork itself to the background to launch the kernel") + } + }; + + let pause = std::time::Duration::from_millis(100); + std::thread::sleep(pause); + self.save_code("kernel_launched\n".to_owned()); + + Err(SniprunError::CustomError( + "Deno kernel launched, re-run your snippet".to_owned(), + )) + } + } + + fn add_boilerplate_repl(&mut self) -> Result<(), SniprunError> { + self.add_boilerplate()?; + let start_mark = String::from("\nconsole.log(\"sniprun_started_id=") + + &self.current_output_id.to_string() + + "\")\n"; + let end_mark = String::from("\nconsole.log(\"sniprun_finished_id=") + + &self.current_output_id.to_string() + + "\")\n"; + let start_mark_err = String::from("\nconsole.error(\"sniprun_started_id=") + + &self.current_output_id.to_string() + + "\")\n"; + let end_mark_err = String::from("\nconsole.error(\"sniprun_finished_id=") + + &self.current_output_id.to_string() + + "\")\n"; + + // Removing empty lines + // self.code = self + // .code + // .lines() + // .filter(|l| !l.trim().is_empty()) + // .collect::>() + // .join("\n"); + + let all_code = String::from("\n") + &self.code + "\n\n"; + self.code = start_mark + &start_mark_err + &all_code + &end_mark + &end_mark_err; + Ok(()) + } + + fn build_repl(&mut self) -> Result<(), SniprunError> { + self.build() + } + + fn execute_repl(&mut self) -> Result { + let send_repl_cmd = self.data.sniprun_root_dir.clone() + "/ressources/launcher_repl.sh"; + info!("running launcher {}", send_repl_cmd); + let res = Command::new(send_repl_cmd) + .arg(self.main_file_path.clone()) + .arg(self.cache_dir.clone() + "/fifo_repl/pipe_in") + .spawn(); + info!("cmd status: {:?}", res); + res.expect("could not run launcher"); + // info!("launcher launched : {:?}", res); + + let outfile = self.cache_dir.clone() + "/fifo_repl/out_file"; + let errfile = self.cache_dir.clone() + "/fifo_repl/err_file"; + info!("outfile : {:?}", outfile); + self.wait_out_file(outfile, errfile, self.current_output_id) + } +} + +#[cfg(test)] +mod test_ts_js_deno_original { + use super::*; + use serial_test::serial; + + #[test] + #[serial(deno)] + fn simple_print() { + let mut data = DataHolder::new(); + + //inspired from Rust syntax + data.current_bloc = String::from("let message: string = 'Hi';\nconsole.log(message);"); + let mut interpreter = JS_TS_deno::new(data); + let res = interpreter.run_at_level(SupportLevel::Bloc); + + // -> should panic if not an Ok() + let string_result = res.unwrap(); + + // -> compare result with predicted + assert_eq!(string_result, "Hi\n"); + } + #[test] + #[serial(deno)] + fn print_repl() { + let mut data = DataHolder::new(); + data.current_bloc = String::from("let message: string = 'Hi';\nconsole.log(message);"); + let mut interpreter = JS_TS_deno::new(data); + let res = interpreter.run_at_level_repl(SupportLevel::Bloc); + assert!(res.is_err()); + } +} diff --git a/src/interpreters/TypeScript_original.rs b/src/interpreters/TypeScript_original.rs index 754a3076..955b19e6 100644 --- a/src/interpreters/TypeScript_original.rs +++ b/src/interpreters/TypeScript_original.rs @@ -137,7 +137,6 @@ impl Interpreter for TypeScript_original { mod test_typescript_original { use super::*; #[test] - #[cfg_attr(feature = "ignore_in_ci", ignore)] fn simple_print() { let mut data = DataHolder::new(); diff --git a/src/launcher.rs b/src/launcher.rs index 5789d88d..621b53ad 100644 --- a/src/launcher.rs +++ b/src/launcher.rs @@ -135,14 +135,14 @@ impl Launcher { v.push("\nAvailable interpreters and languages".to_owned()); - let separator = "|--------------------------|--------------|---------------|-------------|------------|--------------|".to_string(); + let separator = "|--------------------------|---------------|---------------|-------------|------------|--------------|".to_string(); v.push(separator.clone()); - v.push("| Interpreter | Language | Support Level | Default for | REPL | REPL enabled |".to_string()); - v.push("| | | | filetype | capability | by default |".to_string()); + v.push("| Interpreter | Language | Support Level | Default for | REPL | REPL enabled |".to_string()); + v.push("| | | | filetype | capability | by default |".to_string()); let mut temp_vec = vec![]; iter_types! { - let line = format!("| {:<25}| {:<13}| {:<14}|{:^13}|{:^12}|{:^14}|", + let line = format!("| {:<25}| {:<14}| {:<14}|{:^13}|{:^12}|{:^14}|", Current::get_name(), Current::get_supported_languages().get(0).unwrap_or(&"".to_string()), Current::get_max_support_level().to_string(),