mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-05 15:32:58 +00:00
feat: make snippet executors multiplatform
This commit is contained in:
parent
480f9475cc
commit
a2df29ddbe
31
build.rs
31
build.rs
@ -34,39 +34,8 @@ fn build_themes(out_dir: &str) -> io::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_executors(out_dir: &str) -> io::Result<()> {
|
|
||||||
let output_path = format!("{out_dir}/executors.rs");
|
|
||||||
let mut output_file = BufWriter::new(File::create(output_path)?);
|
|
||||||
output_file.write_all(b"use std::collections::BTreeMap as Map;\n")?;
|
|
||||||
output_file.write_all(b"use once_cell::sync::Lazy;\n")?;
|
|
||||||
output_file.write_all(b"static EXECUTORS: Lazy<Map<crate::markdown::elements::CodeLanguage, &'static [u8]>> = Lazy::new(|| Map::from([\n")?;
|
|
||||||
|
|
||||||
let mut paths = fs::read_dir("executors")?.collect::<io::Result<Vec<_>>>()?;
|
|
||||||
paths.sort_by_key(|e| e.path());
|
|
||||||
for file in paths {
|
|
||||||
let metadata = file.metadata()?;
|
|
||||||
if !metadata.is_file() {
|
|
||||||
panic!("found non file in executors directory");
|
|
||||||
}
|
|
||||||
let path = file.path();
|
|
||||||
let contents = fs::read(&path)?;
|
|
||||||
let file_name = path.file_name().unwrap().to_string_lossy();
|
|
||||||
let (language, extension) = file_name.split_once('.').unwrap();
|
|
||||||
if extension != "sh" {
|
|
||||||
panic!("extension must be 'sh'");
|
|
||||||
}
|
|
||||||
output_file.write_all(format!("(\"{language}\".parse().unwrap(), {contents:?}.as_slice()),\n").as_bytes())?;
|
|
||||||
}
|
|
||||||
output_file.write_all(b"]));\n")?;
|
|
||||||
|
|
||||||
// Rebuild if anything changes.
|
|
||||||
println!("cargo:rerun-if-changed=executors");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
let out_dir = env::var("OUT_DIR").unwrap();
|
||||||
build_themes(&out_dir)?;
|
build_themes(&out_dir)?;
|
||||||
build_executors(&out_dir)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -211,6 +211,38 @@
|
|||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"LanguageSnippetExecutionConfig": {
|
||||||
|
"description": "The snippet execution configuration for a specific programming language.",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"commands",
|
||||||
|
"filename"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"commands": {
|
||||||
|
"description": "The commands to be run when executing snippets for this programming language.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"description": "The environment variables to set before invoking every command.",
|
||||||
|
"default": {},
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filename": {
|
||||||
|
"description": "The filename to use for the snippet input file.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MermaidConfig": {
|
"MermaidConfig": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -293,6 +325,13 @@
|
|||||||
"enable"
|
"enable"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"custom": {
|
||||||
|
"description": "Custom snippet executors.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/definitions/LanguageSnippetExecutionConfig"
|
||||||
|
}
|
||||||
|
},
|
||||||
"enable": {
|
"enable": {
|
||||||
"description": "Whether to enable snippet execution.",
|
"description": "Whether to enable snippet execution.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
73
executors.yaml
Normal file
73
executors.yaml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
bash:
|
||||||
|
filename: script.sh
|
||||||
|
commands:
|
||||||
|
- ["bash", "script.sh"]
|
||||||
|
c++:
|
||||||
|
filename: snippet.cpp
|
||||||
|
commands:
|
||||||
|
- ["g++", "-std=c++20", "snippet.cpp", "-o", "snippet"]
|
||||||
|
- ["./snippet"]
|
||||||
|
c:
|
||||||
|
filename: snippet.c
|
||||||
|
commands:
|
||||||
|
- ["gcc", "snippet.c", "-o", "snippet"]
|
||||||
|
- ["./snippet"]
|
||||||
|
fish:
|
||||||
|
filename: script.fish
|
||||||
|
commands:
|
||||||
|
- ["fish", "script.fish"]
|
||||||
|
go:
|
||||||
|
filename: snippet.go
|
||||||
|
environment:
|
||||||
|
GO11MODULE: off
|
||||||
|
commands:
|
||||||
|
- ["go", "run", "snippet.go"]
|
||||||
|
java:
|
||||||
|
filename: Snippet.java
|
||||||
|
commands:
|
||||||
|
- ["java", "Snippet.java"]
|
||||||
|
js:
|
||||||
|
filename: snippet.js
|
||||||
|
commands:
|
||||||
|
- ["node", "snippet.js"]
|
||||||
|
kotlin:
|
||||||
|
filename: snippet.kts
|
||||||
|
commands:
|
||||||
|
- ["kotlinc", "-script", "script.kts"]
|
||||||
|
lua:
|
||||||
|
filename: snippet.lua
|
||||||
|
commands:
|
||||||
|
- ["lua", "snippet.lua"]
|
||||||
|
nushell:
|
||||||
|
filename: snippet.nu
|
||||||
|
commands:
|
||||||
|
- ["nu", "snippet.nu"]
|
||||||
|
perl:
|
||||||
|
filename: snippet.pl
|
||||||
|
commands:
|
||||||
|
- ["perl", "snippet.pl"]
|
||||||
|
python:
|
||||||
|
filename: snippet.py
|
||||||
|
commands:
|
||||||
|
- ["python", "-u", "snippet.py"]
|
||||||
|
ruby:
|
||||||
|
filename: snippet.rb
|
||||||
|
commands:
|
||||||
|
- ["ruby", "snippet.rb"]
|
||||||
|
rust-script:
|
||||||
|
filename: snippet.rs
|
||||||
|
commands:
|
||||||
|
- ["rust-script", "snippet.rs"]
|
||||||
|
rust:
|
||||||
|
filename: snippet.rs
|
||||||
|
commands:
|
||||||
|
- ["rustc", "--crate-name", "presenterm_snippet", "snippet.rs", "-o", "snippet"]
|
||||||
|
- ["./snippet"]
|
||||||
|
sh:
|
||||||
|
filename: script.sh
|
||||||
|
commands:
|
||||||
|
- ["sh", "script.sh"]
|
||||||
|
zsh:
|
||||||
|
filename: script.sh
|
||||||
|
commands:
|
||||||
|
- ["zsh", "script.sh"]
|
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
temp=$(mktemp)
|
|
||||||
g++ -std=c++20 -x c++ "$1" -o "$temp"
|
|
||||||
"$temp"
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
temp=$(mktemp)
|
|
||||||
gcc -x c "$1" -o "$temp"
|
|
||||||
"$temp"
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
export GO111MODULE=off
|
|
||||||
tempdir=$(mktemp -d)
|
|
||||||
cd "$tempdir"
|
|
||||||
mv "$1" main.go
|
|
||||||
go run main.go
|
|
||||||
rm -rf "$tempdir"
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
tempdir=$(mktemp -d)
|
|
||||||
cd "$tempdir"
|
|
||||||
cp "$1" Main.java
|
|
||||||
java Main.java
|
|
||||||
rm -rf "$tempdir"
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
node "$1"
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
tempdir=$(mktemp -d)
|
|
||||||
cd "$tempdir"
|
|
||||||
cp "$1" script.kts
|
|
||||||
kotlinc -script script.kts
|
|
||||||
rm -rf "$tempdir"
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
lua "$1"
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
nu "$1"
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
perl "$1"
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
exec python -u "$1"
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
ruby "$1"
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
rust-script "$1"
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
temp=$(mktemp)
|
|
||||||
rustc --crate-name "presenterm_snippet" "$1" -o "$temp"
|
|
||||||
"$temp"
|
|
@ -27,7 +27,7 @@
|
|||||||
"src"
|
"src"
|
||||||
"themes"
|
"themes"
|
||||||
"bat"
|
"bat"
|
||||||
"executors"
|
"executors.yaml"
|
||||||
];
|
];
|
||||||
|
|
||||||
buildSrc = flakeboxLib.filterSubPaths {
|
buildSrc = flakeboxLib.filterSubPaths {
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
input::user::KeyBinding,
|
input::user::KeyBinding,
|
||||||
|
markdown::elements::CodeLanguage,
|
||||||
media::{emulator::TerminalEmulator, kitty::KittyMode},
|
media::{emulator::TerminalEmulator, kitty::KittyMode},
|
||||||
GraphicsMode,
|
GraphicsMode,
|
||||||
};
|
};
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{fs, io, path::Path};
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
fs, io,
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
@ -134,6 +139,10 @@ pub struct SnippetConfig {
|
|||||||
pub struct SnippetExecConfig {
|
pub struct SnippetExecConfig {
|
||||||
/// Whether to enable snippet execution.
|
/// Whether to enable snippet execution.
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
|
|
||||||
|
/// Custom snippet executors.
|
||||||
|
#[serde(default)]
|
||||||
|
pub custom: BTreeMap<CodeLanguage, LanguageSnippetExecutionConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Deserialize, JsonSchema)]
|
||||||
@ -190,6 +199,20 @@ pub(crate) fn default_mermaid_scale() -> u32 {
|
|||||||
2
|
2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The snippet execution configuration for a specific programming language.
|
||||||
|
#[derive(Clone, Debug, Deserialize, JsonSchema)]
|
||||||
|
pub struct LanguageSnippetExecutionConfig {
|
||||||
|
/// The filename to use for the snippet input file.
|
||||||
|
pub filename: String,
|
||||||
|
|
||||||
|
/// The environment variables to set before invoking every command.
|
||||||
|
#[serde(default)]
|
||||||
|
pub environment: HashMap<String, String>,
|
||||||
|
|
||||||
|
/// The commands to be run when executing snippets for this programming language.
|
||||||
|
pub commands: Vec<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, ValueEnum, JsonSchema)]
|
#[derive(Clone, Debug, Default, Deserialize, ValueEnum, JsonSchema)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum ImageProtocol {
|
pub enum ImageProtocol {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
execute::CodeExecutor,
|
execute::SnippetExecutor,
|
||||||
input::{
|
input::{
|
||||||
source::Command,
|
source::Command,
|
||||||
user::{CommandKeyBindings, UserInput},
|
user::{CommandKeyBindings, UserInput},
|
||||||
@ -102,7 +102,7 @@ impl<W: TerminalWrite> ThemesDemo<W> {
|
|||||||
let mut resources = Resources::new("non_existent", image_registry.clone());
|
let mut resources = Resources::new("non_existent", image_registry.clone());
|
||||||
let mut third_party = ThirdPartyRender::default();
|
let mut third_party = ThirdPartyRender::default();
|
||||||
let options = PresentationBuilderOptions::default();
|
let options = PresentationBuilderOptions::default();
|
||||||
let executer = Rc::new(CodeExecutor::default());
|
let executer = Rc::new(SnippetExecutor::default());
|
||||||
let bindings_config = Default::default();
|
let bindings_config = Default::default();
|
||||||
let builder = PresentationBuilder::new(
|
let builder = PresentationBuilder::new(
|
||||||
theme,
|
theme,
|
||||||
|
266
src/execute.rs
266
src/execute.rs
@ -1,60 +1,54 @@
|
|||||||
//! Code execution.
|
//! Code execution.
|
||||||
|
|
||||||
use crate::markdown::elements::{Code, CodeLanguage};
|
use crate::{
|
||||||
|
custom::LanguageSnippetExecutionConfig,
|
||||||
|
markdown::elements::{Code, CodeLanguage},
|
||||||
|
};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use os_pipe::PipeReader;
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::{BTreeMap, HashMap},
|
||||||
ffi::OsStr,
|
fs::File,
|
||||||
fs,
|
|
||||||
io::{self, BufRead, BufReader, Write},
|
io::{self, BufRead, BufReader, Write},
|
||||||
path::{Path, PathBuf},
|
process::{self, Child, Stdio},
|
||||||
process::{self, Stdio},
|
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/executors.rs"));
|
static EXECUTORS: Lazy<BTreeMap<CodeLanguage, LanguageSnippetExecutionConfig>> =
|
||||||
|
Lazy::new(|| serde_yaml::from_slice(include_bytes!("../executors.yaml")).expect("executors.yaml is broken"));
|
||||||
|
|
||||||
/// Allows executing code.
|
/// Allows executing code.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CodeExecutor {
|
pub struct SnippetExecutor {
|
||||||
custom_executors: BTreeMap<CodeLanguage, Vec<u8>>,
|
executors: BTreeMap<CodeLanguage, LanguageSnippetExecutionConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeExecutor {
|
impl SnippetExecutor {
|
||||||
pub fn load(executors_path: &Path) -> Result<Self, LoadExecutorsError> {
|
pub fn new(
|
||||||
let mut custom_executors = BTreeMap::new();
|
custom_executors: BTreeMap<CodeLanguage, LanguageSnippetExecutionConfig>,
|
||||||
if let Ok(paths) = fs::read_dir(executors_path) {
|
) -> Result<Self, InvalidSnippetConfig> {
|
||||||
for executor in paths {
|
let mut executors = EXECUTORS.clone();
|
||||||
let executor = executor?;
|
executors.extend(custom_executors);
|
||||||
let path = executor.path();
|
for (language, config) in &executors {
|
||||||
let filename = path.file_name().unwrap_or_default().to_string_lossy();
|
if config.filename.is_empty() {
|
||||||
let Some((name, extension)) = filename.split_once('.') else {
|
return Err(InvalidSnippetConfig(language.clone(), "filename is empty"));
|
||||||
return Err(LoadExecutorsError::InvalidExecutor(path, "no extension"));
|
}
|
||||||
};
|
if config.commands.is_empty() {
|
||||||
if extension != "sh" {
|
return Err(InvalidSnippetConfig(language.clone(), "no commands given"));
|
||||||
return Err(LoadExecutorsError::InvalidExecutor(path, "non .sh extension"));
|
}
|
||||||
|
for command in &config.commands {
|
||||||
|
if command.is_empty() {
|
||||||
|
return Err(InvalidSnippetConfig(language.clone(), "empty command given"));
|
||||||
}
|
}
|
||||||
let language: CodeLanguage = match name.parse() {
|
|
||||||
Ok(CodeLanguage::Unknown(_)) => {
|
|
||||||
return Err(LoadExecutorsError::InvalidExecutor(path, "unknown language"));
|
|
||||||
}
|
|
||||||
Ok(language) => language,
|
|
||||||
Err(_) => return Err(LoadExecutorsError::InvalidExecutor(path, "invalid code language")),
|
|
||||||
};
|
|
||||||
let file_contents = fs::read(path)?;
|
|
||||||
custom_executors.insert(language, file_contents);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Self { custom_executors })
|
Ok(Self { executors })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_execution_supported(&self, language: &CodeLanguage) -> bool {
|
pub(crate) fn is_execution_supported(&self, language: &CodeLanguage) -> bool {
|
||||||
if matches!(language, CodeLanguage::Shell(_)) {
|
self.executors.contains_key(language)
|
||||||
true
|
|
||||||
} else {
|
|
||||||
EXECUTORS.contains_key(language) || self.custom_executors.contains_key(language)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a piece of code.
|
/// Execute a piece of code.
|
||||||
@ -62,70 +56,39 @@ impl CodeExecutor {
|
|||||||
if !code.attributes.execute {
|
if !code.attributes.execute {
|
||||||
return Err(CodeExecuteError::NotExecutableCode);
|
return Err(CodeExecuteError::NotExecutableCode);
|
||||||
}
|
}
|
||||||
match &code.language {
|
let Some(config) = self.executors.get(&code.language) else {
|
||||||
CodeLanguage::Shell(interpreter) => {
|
return Err(CodeExecuteError::UnsupportedExecution);
|
||||||
let args: &[&str] = &[];
|
};
|
||||||
Self::execute_shell(interpreter, code.executable_contents().as_bytes(), args)
|
Self::execute_lang(config, code.executable_contents().as_bytes())
|
||||||
}
|
|
||||||
lang => {
|
|
||||||
let executor = self.executor(lang).ok_or(CodeExecuteError::UnsupportedExecution)?;
|
|
||||||
Self::execute_lang(executor, code.executable_contents().as_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn executor(&self, language: &CodeLanguage) -> Option<&[u8]> {
|
fn execute_lang(config: &LanguageSnippetExecutionConfig, code: &[u8]) -> Result<ExecutionHandle, CodeExecuteError> {
|
||||||
if let Some(executor) = self.custom_executors.get(language) {
|
let script_dir =
|
||||||
return Some(executor);
|
tempfile::Builder::default().prefix(".presenterm").tempdir().map_err(CodeExecuteError::TempDir)?;
|
||||||
|
let snippet_path = script_dir.path().join(&config.filename);
|
||||||
|
{
|
||||||
|
let mut snippet_file = File::create(snippet_path).map_err(CodeExecuteError::TempDir)?;
|
||||||
|
snippet_file.write_all(code).map_err(CodeExecuteError::TempDir)?;
|
||||||
}
|
}
|
||||||
EXECUTORS.get(language).copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_shell<S>(interpreter: &str, code: &[u8], args: &[S]) -> Result<ExecutionHandle, CodeExecuteError>
|
|
||||||
where
|
|
||||||
S: AsRef<OsStr>,
|
|
||||||
{
|
|
||||||
let mut output_file = NamedTempFile::new().map_err(CodeExecuteError::TempFile)?;
|
|
||||||
output_file.write_all(code).map_err(CodeExecuteError::TempFile)?;
|
|
||||||
output_file.flush().map_err(CodeExecuteError::TempFile)?;
|
|
||||||
let (reader, writer) = os_pipe::pipe().map_err(CodeExecuteError::Pipe)?;
|
|
||||||
let writer_clone = writer.try_clone().map_err(CodeExecuteError::Pipe)?;
|
|
||||||
let process_handle = process::Command::new("/usr/bin/env")
|
|
||||||
.arg(interpreter)
|
|
||||||
.arg(output_file.path())
|
|
||||||
.args(args)
|
|
||||||
.stdin(Stdio::null())
|
|
||||||
.stdout(writer)
|
|
||||||
.stderr(writer_clone)
|
|
||||||
.spawn()
|
|
||||||
.map_err(CodeExecuteError::SpawnProcess)?;
|
|
||||||
|
|
||||||
let state: Arc<Mutex<ExecutionState>> = Default::default();
|
let state: Arc<Mutex<ExecutionState>> = Default::default();
|
||||||
let reader_handle = ProcessReader::spawn(process_handle, state.clone(), output_file, reader);
|
let reader_handle =
|
||||||
let handle = ExecutionHandle { state, reader_handle, program_path: None };
|
CommandsRunner::spawn(state.clone(), script_dir, config.commands.clone(), config.environment.clone());
|
||||||
Ok(handle)
|
let handle = ExecutionHandle { state, reader_handle };
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_lang(executor: &[u8], code: &[u8]) -> Result<ExecutionHandle, CodeExecuteError> {
|
|
||||||
let mut code_file = NamedTempFile::new().map_err(CodeExecuteError::TempFile)?;
|
|
||||||
code_file.write_all(code).map_err(CodeExecuteError::TempFile)?;
|
|
||||||
|
|
||||||
let path = code_file.path();
|
|
||||||
let mut handle = Self::execute_shell("bash", executor, &[path])?;
|
|
||||||
handle.program_path = Some(code_file);
|
|
||||||
Ok(handle)
|
Ok(handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error during the load of custom executors.
|
impl Default for SnippetExecutor {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(Default::default()).expect("initialization failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An invalid executor was found.
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum LoadExecutorsError {
|
#[error("invalid snippet execution for '{0:?}': {1}")]
|
||||||
#[error("io: {0}")]
|
pub struct InvalidSnippetConfig(CodeLanguage, &'static str);
|
||||||
Io(#[from] io::Error),
|
|
||||||
|
|
||||||
#[error("invalid executor '{0}': {1}")]
|
|
||||||
InvalidExecutor(PathBuf, &'static str),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error during the execution of some code.
|
/// An error during the execution of some code.
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@ -136,11 +99,11 @@ pub(crate) enum CodeExecuteError {
|
|||||||
#[error("code is not marked for execution")]
|
#[error("code is not marked for execution")]
|
||||||
NotExecutableCode,
|
NotExecutableCode,
|
||||||
|
|
||||||
#[error("error creating temporary file: {0}")]
|
#[error("error creating temporary directory: {0}")]
|
||||||
TempFile(io::Error),
|
TempDir(io::Error),
|
||||||
|
|
||||||
#[error("error spawning process: {0}")]
|
#[error("error spawning process '{0}': {1}")]
|
||||||
SpawnProcess(io::Error),
|
SpawnProcess(String, io::Error),
|
||||||
|
|
||||||
#[error("error creating pipe: {0}")]
|
#[error("error creating pipe: {0}")]
|
||||||
Pipe(io::Error),
|
Pipe(io::Error),
|
||||||
@ -152,7 +115,6 @@ pub(crate) struct ExecutionHandle {
|
|||||||
state: Arc<Mutex<ExecutionState>>,
|
state: Arc<Mutex<ExecutionState>>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
reader_handle: thread::JoinHandle<()>,
|
reader_handle: thread::JoinHandle<()>,
|
||||||
program_path: Option<NamedTempFile>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionHandle {
|
impl ExecutionHandle {
|
||||||
@ -163,38 +125,75 @@ impl ExecutionHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes the output of a process and stores it in a shared state.
|
/// Consumes the output of a process and stores it in a shared state.
|
||||||
struct ProcessReader {
|
struct CommandsRunner {
|
||||||
handle: process::Child,
|
|
||||||
state: Arc<Mutex<ExecutionState>>,
|
state: Arc<Mutex<ExecutionState>>,
|
||||||
#[allow(dead_code)]
|
script_directory: TempDir,
|
||||||
file_handle: NamedTempFile,
|
|
||||||
reader: os_pipe::PipeReader,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessReader {
|
impl CommandsRunner {
|
||||||
fn spawn(
|
fn spawn(
|
||||||
handle: process::Child,
|
|
||||||
state: Arc<Mutex<ExecutionState>>,
|
state: Arc<Mutex<ExecutionState>>,
|
||||||
file_handle: NamedTempFile,
|
script_directory: TempDir,
|
||||||
reader: os_pipe::PipeReader,
|
commands: Vec<Vec<String>>,
|
||||||
|
env: HashMap<String, String>,
|
||||||
) -> thread::JoinHandle<()> {
|
) -> thread::JoinHandle<()> {
|
||||||
let reader = Self { handle, state, file_handle, reader };
|
let reader = Self { state, script_directory };
|
||||||
thread::spawn(|| reader.run())
|
thread::spawn(|| reader.run(commands, env))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(mut self) {
|
fn run(self, commands: Vec<Vec<String>>, env: HashMap<String, String>) {
|
||||||
let _ = Self::process_output(self.state.clone(), self.reader);
|
let mut last_result = true;
|
||||||
let success = match self.handle.wait() {
|
for command in commands {
|
||||||
Ok(code) => code.success(),
|
last_result = self.run_command(command, &env);
|
||||||
_ => false,
|
if !last_result {
|
||||||
};
|
break;
|
||||||
let status = match success {
|
}
|
||||||
|
}
|
||||||
|
let status = match last_result {
|
||||||
true => ProcessStatus::Success,
|
true => ProcessStatus::Success,
|
||||||
false => ProcessStatus::Failure,
|
false => ProcessStatus::Failure,
|
||||||
};
|
};
|
||||||
self.state.lock().unwrap().status = status;
|
self.state.lock().unwrap().status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_command(&self, command: Vec<String>, env: &HashMap<String, String>) -> bool {
|
||||||
|
let (mut child, reader) = match self.launch_process(command, env) {
|
||||||
|
Ok(inner) => inner,
|
||||||
|
Err(e) => {
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.status = ProcessStatus::Failure;
|
||||||
|
state.output.push(e.to_string());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let _ = Self::process_output(self.state.clone(), reader);
|
||||||
|
|
||||||
|
match child.wait() {
|
||||||
|
Ok(code) => code.success(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn launch_process(
|
||||||
|
&self,
|
||||||
|
commands: Vec<String>,
|
||||||
|
env: &HashMap<String, String>,
|
||||||
|
) -> Result<(Child, PipeReader), CodeExecuteError> {
|
||||||
|
let (reader, writer) = os_pipe::pipe().map_err(CodeExecuteError::Pipe)?;
|
||||||
|
let writer_clone = writer.try_clone().map_err(CodeExecuteError::Pipe)?;
|
||||||
|
let (command, args) = commands.split_first().expect("no commands");
|
||||||
|
let child = process::Command::new(command)
|
||||||
|
.args(args)
|
||||||
|
.envs(env)
|
||||||
|
.current_dir(self.script_directory.path())
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(writer)
|
||||||
|
.stderr(writer_clone)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| CodeExecuteError::SpawnProcess(command.clone(), e))?;
|
||||||
|
Ok((child, reader))
|
||||||
|
}
|
||||||
|
|
||||||
fn process_output(state: Arc<Mutex<ExecutionState>>, reader: os_pipe::PipeReader) -> io::Result<()> {
|
fn process_output(state: Arc<Mutex<ExecutionState>>, reader: os_pipe::PipeReader) -> io::Result<()> {
|
||||||
let reader = BufReader::new(reader);
|
let reader = BufReader::new(reader);
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
@ -231,8 +230,6 @@ impl ProcessStatus {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use tempfile::tempdir;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::markdown::elements::CodeAttributes;
|
use crate::markdown::elements::CodeAttributes;
|
||||||
|
|
||||||
@ -247,7 +244,7 @@ echo 'bye'"
|
|||||||
language: CodeLanguage::Shell("sh".into()),
|
language: CodeLanguage::Shell("sh".into()),
|
||||||
attributes: CodeAttributes { execute: true, ..Default::default() },
|
attributes: CodeAttributes { execute: true, ..Default::default() },
|
||||||
};
|
};
|
||||||
let handle = CodeExecutor::default().execute(&code).expect("execution failed");
|
let handle = SnippetExecutor::default().execute(&code).expect("execution failed");
|
||||||
let state = loop {
|
let state = loop {
|
||||||
let state = handle.state();
|
let state = handle.state();
|
||||||
if state.status.is_finished() {
|
if state.status.is_finished() {
|
||||||
@ -267,7 +264,7 @@ echo 'bye'"
|
|||||||
language: CodeLanguage::Shell("sh".into()),
|
language: CodeLanguage::Shell("sh".into()),
|
||||||
attributes: CodeAttributes { execute: false, ..Default::default() },
|
attributes: CodeAttributes { execute: false, ..Default::default() },
|
||||||
};
|
};
|
||||||
let result = CodeExecutor::default().execute(&code);
|
let result = SnippetExecutor::default().execute(&code);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +280,7 @@ echo 'hello world'
|
|||||||
language: CodeLanguage::Shell("sh".into()),
|
language: CodeLanguage::Shell("sh".into()),
|
||||||
attributes: CodeAttributes { execute: true, ..Default::default() },
|
attributes: CodeAttributes { execute: true, ..Default::default() },
|
||||||
};
|
};
|
||||||
let handle = CodeExecutor::default().execute(&code).expect("execution failed");
|
let handle = SnippetExecutor::default().execute(&code).expect("execution failed");
|
||||||
let state = loop {
|
let state = loop {
|
||||||
let state = handle.state();
|
let state = handle.state();
|
||||||
if state.status.is_finished() {
|
if state.status.is_finished() {
|
||||||
@ -308,7 +305,7 @@ echo 'hello world'
|
|||||||
language: CodeLanguage::Shell("sh".into()),
|
language: CodeLanguage::Shell("sh".into()),
|
||||||
attributes: CodeAttributes { execute: true, ..Default::default() },
|
attributes: CodeAttributes { execute: true, ..Default::default() },
|
||||||
};
|
};
|
||||||
let handle = CodeExecutor::default().execute(&code).expect("execution failed");
|
let handle = SnippetExecutor::default().execute(&code).expect("execution failed");
|
||||||
let state = loop {
|
let state = loop {
|
||||||
let state = handle.state();
|
let state = handle.state();
|
||||||
if state.status.is_finished() {
|
if state.status.is_finished() {
|
||||||
@ -322,28 +319,7 @@ echo 'hello world'
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn custom_executor() {
|
fn built_in_executors() {
|
||||||
let dir = tempdir().unwrap();
|
SnippetExecutor::new(Default::default()).expect("invalid default executors");
|
||||||
fs::write(dir.path().join("rust.sh"), "hi").expect("writing script failed");
|
|
||||||
let executor = CodeExecutor::load(dir.path()).expect("load filed");
|
|
||||||
|
|
||||||
let script = executor.custom_executors.get(&CodeLanguage::Rust).expect("rust not found");
|
|
||||||
assert_eq!(script, b"hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unknown_executor_language() {
|
|
||||||
let dir = tempdir().unwrap();
|
|
||||||
fs::write(dir.path().join("potato.sh"), "").expect("writing script failed");
|
|
||||||
let executor = CodeExecutor::load(dir.path());
|
|
||||||
assert!(matches!(executor, Err(LoadExecutorsError::InvalidExecutor(_, _))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_executor_extension() {
|
|
||||||
let dir = tempdir().unwrap();
|
|
||||||
fs::write(dir.path().join("rust.potato"), "").expect("writing script failed");
|
|
||||||
let executor = CodeExecutor::load(dir.path());
|
|
||||||
assert!(matches!(executor, Err(LoadExecutorsError::InvalidExecutor(_, _))));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
custom::KeyBindingsConfig,
|
custom::KeyBindingsConfig,
|
||||||
execute::CodeExecutor,
|
execute::SnippetExecutor,
|
||||||
markdown::parse::ParseError,
|
markdown::parse::ParseError,
|
||||||
media::{
|
media::{
|
||||||
image::{Image, ImageSource},
|
image::{Image, ImageSource},
|
||||||
@ -30,7 +30,7 @@ pub struct Exporter<'a> {
|
|||||||
default_theme: &'a PresentationTheme,
|
default_theme: &'a PresentationTheme,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
third_party: ThirdPartyRender,
|
third_party: ThirdPartyRender,
|
||||||
code_executor: Rc<CodeExecutor>,
|
code_executor: Rc<SnippetExecutor>,
|
||||||
themes: Themes,
|
themes: Themes,
|
||||||
options: PresentationBuilderOptions,
|
options: PresentationBuilderOptions,
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ impl<'a> Exporter<'a> {
|
|||||||
default_theme: &'a PresentationTheme,
|
default_theme: &'a PresentationTheme,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
third_party: ThirdPartyRender,
|
third_party: ThirdPartyRender,
|
||||||
code_executor: Rc<CodeExecutor>,
|
code_executor: Rc<SnippetExecutor>,
|
||||||
themes: Themes,
|
themes: Themes,
|
||||||
options: PresentationBuilderOptions,
|
options: PresentationBuilderOptions,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -23,7 +23,7 @@ pub(crate) mod tools;
|
|||||||
pub use crate::{
|
pub use crate::{
|
||||||
custom::{Config, ImageProtocol, ValidateOverflows},
|
custom::{Config, ImageProtocol, ValidateOverflows},
|
||||||
demo::ThemesDemo,
|
demo::ThemesDemo,
|
||||||
execute::CodeExecutor,
|
execute::SnippetExecutor,
|
||||||
export::{ExportError, Exporter},
|
export::{ExportError, Exporter},
|
||||||
input::source::CommandSource,
|
input::source::CommandSource,
|
||||||
markdown::parse::MarkdownParser,
|
markdown::parse::MarkdownParser,
|
||||||
|
11
src/main.rs
11
src/main.rs
@ -2,9 +2,10 @@ use clap::{error::ErrorKind, CommandFactory, Parser};
|
|||||||
use comrak::Arena;
|
use comrak::Arena;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use presenterm::{
|
use presenterm::{
|
||||||
CodeExecutor, CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol,
|
CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, ImageRegistry,
|
||||||
ImageRegistry, MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet,
|
MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet, Presenter,
|
||||||
Presenter, PresenterOptions, Resources, Themes, ThemesDemo, ThirdPartyConfigs, ThirdPartyRender, ValidateOverflows,
|
PresenterOptions, Resources, SnippetExecutor, Themes, ThemesDemo, ThirdPartyConfigs, ThirdPartyRender,
|
||||||
|
ValidateOverflows,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
env, io,
|
env, io,
|
||||||
@ -91,7 +92,7 @@ fn create_splash() -> String {
|
|||||||
struct Customizations {
|
struct Customizations {
|
||||||
config: Config,
|
config: Config,
|
||||||
themes: Themes,
|
themes: Themes,
|
||||||
code_executor: CodeExecutor,
|
code_executor: SnippetExecutor,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_customizations(config_file_path: Option<PathBuf>) -> Result<Customizations, Box<dyn std::error::Error>> {
|
fn load_customizations(config_file_path: Option<PathBuf>) -> Result<Customizations, Box<dyn std::error::Error>> {
|
||||||
@ -107,7 +108,7 @@ fn load_customizations(config_file_path: Option<PathBuf>) -> Result<Customizatio
|
|||||||
let themes = load_themes(&configs_path)?;
|
let themes = load_themes(&configs_path)?;
|
||||||
let config_file_path = config_file_path.unwrap_or_else(|| configs_path.join("config.yaml"));
|
let config_file_path = config_file_path.unwrap_or_else(|| configs_path.join("config.yaml"));
|
||||||
let config = Config::load(&config_file_path)?;
|
let config = Config::load(&config_file_path)?;
|
||||||
let code_executor = CodeExecutor::load(&configs_path.join("executors"))?;
|
let code_executor = SnippetExecutor::new(config.snippet.exec.custom.clone())?;
|
||||||
Ok(Customizations { config, themes, code_executor })
|
Ok(Customizations { config, themes, code_executor })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::style::TextStyle;
|
use crate::style::TextStyle;
|
||||||
|
use serde_with::DeserializeFromStr;
|
||||||
use std::{convert::Infallible, fmt::Write, iter, ops::Range, path::PathBuf, str::FromStr};
|
use std::{convert::Infallible, fmt::Write, iter, ops::Range, path::PathBuf, str::FromStr};
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
@ -205,7 +206,7 @@ impl Code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The language of a piece of code.
|
/// The language of a piece of code.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, PartialOrd, Ord, DeserializeFromStr)]
|
||||||
pub enum CodeLanguage {
|
pub enum CodeLanguage {
|
||||||
Ada,
|
Ada,
|
||||||
Asp,
|
Asp,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
custom::KeyBindingsConfig,
|
custom::KeyBindingsConfig,
|
||||||
diff::PresentationDiffer,
|
diff::PresentationDiffer,
|
||||||
execute::CodeExecutor,
|
execute::SnippetExecutor,
|
||||||
export::ImageReplacer,
|
export::ImageReplacer,
|
||||||
input::source::{Command, CommandSource},
|
input::source::{Command, CommandSource},
|
||||||
markdown::parse::{MarkdownParser, ParseError},
|
markdown::parse::{MarkdownParser, ParseError},
|
||||||
@ -46,7 +46,7 @@ pub struct Presenter<'a> {
|
|||||||
parser: MarkdownParser<'a>,
|
parser: MarkdownParser<'a>,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
third_party: ThirdPartyRender,
|
third_party: ThirdPartyRender,
|
||||||
code_executor: Rc<CodeExecutor>,
|
code_executor: Rc<SnippetExecutor>,
|
||||||
state: PresenterState,
|
state: PresenterState,
|
||||||
slides_with_pending_async_renders: HashSet<usize>,
|
slides_with_pending_async_renders: HashSet<usize>,
|
||||||
image_printer: Arc<ImagePrinter>,
|
image_printer: Arc<ImagePrinter>,
|
||||||
@ -63,7 +63,7 @@ impl<'a> Presenter<'a> {
|
|||||||
parser: MarkdownParser<'a>,
|
parser: MarkdownParser<'a>,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
third_party: ThirdPartyRender,
|
third_party: ThirdPartyRender,
|
||||||
code_executor: Rc<CodeExecutor>,
|
code_executor: Rc<SnippetExecutor>,
|
||||||
themes: Themes,
|
themes: Themes,
|
||||||
image_printer: Arc<ImagePrinter>,
|
image_printer: Arc<ImagePrinter>,
|
||||||
options: PresenterOptions,
|
options: PresenterOptions,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::{execution::SnippetExecutionDisabledOperation, modals::KeyBindingsModalBuilder};
|
use super::{execution::SnippetExecutionDisabledOperation, modals::KeyBindingsModalBuilder};
|
||||||
use crate::{
|
use crate::{
|
||||||
custom::{KeyBindingsConfig, OptionsConfig},
|
custom::{KeyBindingsConfig, OptionsConfig},
|
||||||
execute::CodeExecutor,
|
execute::SnippetExecutor,
|
||||||
markdown::{
|
markdown::{
|
||||||
elements::{
|
elements::{
|
||||||
Code, CodeLanguage, Highlight, HighlightGroup, ListItem, ListItemType, MarkdownElement, ParagraphElement,
|
Code, CodeLanguage, Highlight, HighlightGroup, ListItem, ListItemType, MarkdownElement, ParagraphElement,
|
||||||
@ -96,7 +96,7 @@ pub(crate) struct PresentationBuilder<'a> {
|
|||||||
chunk_mutators: Vec<Box<dyn ChunkMutator>>,
|
chunk_mutators: Vec<Box<dyn ChunkMutator>>,
|
||||||
slides: Vec<Slide>,
|
slides: Vec<Slide>,
|
||||||
highlighter: CodeHighlighter,
|
highlighter: CodeHighlighter,
|
||||||
code_executor: Rc<CodeExecutor>,
|
code_executor: Rc<SnippetExecutor>,
|
||||||
theme: Cow<'a, PresentationTheme>,
|
theme: Cow<'a, PresentationTheme>,
|
||||||
resources: &'a mut Resources,
|
resources: &'a mut Resources,
|
||||||
third_party: &'a mut ThirdPartyRender,
|
third_party: &'a mut ThirdPartyRender,
|
||||||
@ -117,7 +117,7 @@ impl<'a> PresentationBuilder<'a> {
|
|||||||
default_theme: &'a PresentationTheme,
|
default_theme: &'a PresentationTheme,
|
||||||
resources: &'a mut Resources,
|
resources: &'a mut Resources,
|
||||||
third_party: &'a mut ThirdPartyRender,
|
third_party: &'a mut ThirdPartyRender,
|
||||||
code_executor: Rc<CodeExecutor>,
|
code_executor: Rc<SnippetExecutor>,
|
||||||
themes: &'a Themes,
|
themes: &'a Themes,
|
||||||
image_registry: ImageRegistry,
|
image_registry: ImageRegistry,
|
||||||
bindings_config: KeyBindingsConfig,
|
bindings_config: KeyBindingsConfig,
|
||||||
@ -1081,7 +1081,7 @@ mod test {
|
|||||||
let theme = PresentationTheme::default();
|
let theme = PresentationTheme::default();
|
||||||
let mut resources = Resources::new("/tmp", Default::default());
|
let mut resources = Resources::new("/tmp", Default::default());
|
||||||
let mut third_party = ThirdPartyRender::default();
|
let mut third_party = ThirdPartyRender::default();
|
||||||
let code_executor = Rc::new(CodeExecutor::default());
|
let code_executor = Rc::new(SnippetExecutor::default());
|
||||||
let themes = Themes::default();
|
let themes = Themes::default();
|
||||||
let bindings = KeyBindingsConfig::default();
|
let bindings = KeyBindingsConfig::default();
|
||||||
let builder = PresentationBuilder::new(
|
let builder = PresentationBuilder::new(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::separator::RenderSeparator;
|
use super::separator::RenderSeparator;
|
||||||
use crate::{
|
use crate::{
|
||||||
execute::{CodeExecutor, ExecutionHandle, ExecutionState, ProcessStatus},
|
execute::{ExecutionHandle, ExecutionState, ProcessStatus, SnippetExecutor},
|
||||||
markdown::elements::{Code, Text, TextBlock},
|
markdown::elements::{Code, Text, TextBlock},
|
||||||
presentation::{AsRenderOperations, PreformattedLine, RenderAsync, RenderAsyncState, RenderOperation},
|
presentation::{AsRenderOperations, PreformattedLine, RenderAsync, RenderAsyncState, RenderOperation},
|
||||||
render::properties::WindowSize,
|
render::properties::WindowSize,
|
||||||
@ -12,7 +12,7 @@ use std::{cell::RefCell, mem, rc::Rc};
|
|||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct RunCodeOperationInner {
|
struct RunSnippetOperationInner {
|
||||||
handle: Option<ExecutionHandle>,
|
handle: Option<ExecutionHandle>,
|
||||||
output_lines: Vec<String>,
|
output_lines: Vec<String>,
|
||||||
state: RenderAsyncState,
|
state: RenderAsyncState,
|
||||||
@ -21,24 +21,24 @@ struct RunCodeOperationInner {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct RunSnippetOperation {
|
pub(crate) struct RunSnippetOperation {
|
||||||
code: Code,
|
code: Code,
|
||||||
executor: Rc<CodeExecutor>,
|
executor: Rc<SnippetExecutor>,
|
||||||
default_colors: Colors,
|
default_colors: Colors,
|
||||||
block_colors: Colors,
|
block_colors: Colors,
|
||||||
status_colors: ExecutionStatusBlockStyle,
|
status_colors: ExecutionStatusBlockStyle,
|
||||||
inner: Rc<RefCell<RunCodeOperationInner>>,
|
inner: Rc<RefCell<RunSnippetOperationInner>>,
|
||||||
state_description: RefCell<Text>,
|
state_description: RefCell<Text>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunSnippetOperation {
|
impl RunSnippetOperation {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
code: Code,
|
code: Code,
|
||||||
executor: Rc<CodeExecutor>,
|
executor: Rc<SnippetExecutor>,
|
||||||
default_colors: Colors,
|
default_colors: Colors,
|
||||||
block_colors: Colors,
|
block_colors: Colors,
|
||||||
status_colors: ExecutionStatusBlockStyle,
|
status_colors: ExecutionStatusBlockStyle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let inner =
|
let inner =
|
||||||
RunCodeOperationInner { handle: None, output_lines: Vec::new(), state: RenderAsyncState::default() };
|
RunSnippetOperationInner { handle: None, output_lines: Vec::new(), state: RenderAsyncState::default() };
|
||||||
let running_colors = status_colors.running.clone();
|
let running_colors = status_colors.running.clone();
|
||||||
Self {
|
Self {
|
||||||
code,
|
code,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user