Skip to content

Commit

Permalink
Merge pull request michaelb#140 from michaelb/dev
Browse files Browse the repository at this point in the history
live_mode
  • Loading branch information
michaelb authored Feb 3, 2022
2 parents 8198e73 + 66a3e11 commit e201601
Show file tree
Hide file tree
Showing 25 changed files with 172 additions and 51 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v1.2
- Live mode (a @ChristianChiarulli idea and partial realisation)
- Lower ressources usage for REPL interpreters

## v1.1.2
- auto detection of entry point for many languages
- CFLAGS and other variables
Expand Down
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sniprun"
version = "1.1.2"
version = "1.2"
authors = ["michaelb <[email protected]>"]
edition = "2018"

Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Do either of:
- Position the cursor on a line and `:SnipRun`
- Select some visual range, then `:'<,'>SnipRun`
- Combine a motion with the operator
- Activate the live mode

(through a shortcut!)

Expand Down Expand Up @@ -212,6 +213,8 @@ You can do basically two things: **run** your code selection and **stop** it (in
```
(see mapping [example](README.md#my-usage-recommandation--tricks)),

**Running 'live'** (aka running the current line as you're typing is possible, but it's very important to read the warnings about this, so I'm keeping the instructions in [a separate place](ressources/live_mode.md).

**Bloc mode:** Select the code you want to execute in visual mode and type in:

`:'<,'>SnipRun`
Expand Down Expand Up @@ -321,6 +324,7 @@ require'sniprun'.setup({
borders = 'single' --# display borders around floating windows
--# possible values are 'none', 'single', 'double', or 'shadow'
live_mode_toggle='off' --# live mode toggle, see Usage - Running for more info
})
EOF
```
Expand All @@ -344,6 +348,7 @@ All of sniprun functionnalities:
| :SnipReset | lua require'sniprun'.reset() | \<Plug\>SnipReset |
| :SnipReplMemoryClean | lua require'sniprun'.clear\_repl() | \<Plug\>SnipReplMemoryClean |
| :SnipClose | lua require'sniprun.display'.close() | \<Plug\>SnipClose |
| :SnipLive | lua require'sniprun.live_mode'.toggle()| \<Plug\>SnipLive |
|| lua require'sniprun.api'.run\_range(..)||
|| lua require'sniprun.api'.run\_string(..)||

Expand Down Expand Up @@ -381,7 +386,7 @@ vmap f <Plug>SnipRun
</details>
</p>

- For interpreted languages with simple output, `:%SnipRun` (or a shortcut) may be a more convenient way to run your entire file. When running the whole file, SnipRun supports taking arguments on the command line: `:%SnipRun 5 "yay"` frictionlessly for interpreted languages, and compiled languages with entry point detection implemented (most of them).
- For interpreted languages with simple output, `:%SnipRun` (or a shortcut, wrapping it with `let b:caret=winsaveview()` and `call winrestview(b:caret)` in order to keep the cursor at the current position) may be a more convenient way to run your entire file. When running the whole file, SnipRun supports taking arguments on the command line: `:%SnipRun 5 "yay"` frictionlessly for interpreted languages, and compiled languages with entry point detection implemented (most of them).


While both shorthands and \<Plug> are here to stay, **please use the `<Plug>` style ones in your mappings** or if using from another plugin.
Expand Down
3 changes: 3 additions & 0 deletions doc/sniprun.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ ALL COMMANDS *sniprun-commands*

:SnipClose Clear virtual text and close splits and floating windows created by sniprun

:SnipLive Toggle live mode (read the docs on github.com/michaelb/sniprun first !)
This command is not available by default given how much important knowledge about that is


==============================================================================
CONFIGURATION *sniprun-configuration*
Expand Down
8 changes: 8 additions & 0 deletions lua/sniprun.lua
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ M.config_values = {
SniprunFloatingWinErr = {fg="#881515",ctermfg="DarkRed"},
},

-- whether the user can toggle the live_mode. It's kept as an option so it's not activated by chance
-- by an user that would be unaware of the potentially dangerous behavior
live_mode_toggle='off',

-- auto-filled with the real nvim's PID
neovim_pid=0

}
Expand All @@ -79,6 +84,9 @@ function M.setup(opts)
if key == 'snipruncolors' then
M.custom_highlight = true
end
if key == 'live_mode_toggle' and opts[key] == 'enable' then
require('sniprun.live_mode')
end
M.config_values[key] = value
end
M.configure_keymaps()
Expand Down
49 changes: 49 additions & 0 deletions lua/sniprun/live_mode.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
local M = {}


function M.run()
local sa = require('sniprun.api')
local line = vim.api.nvim_win_get_cursor(0)[1]
local ft = vim.bo.filetype
local opts = require('sniprun').config_values
opts.display = { "VirtualTextOk"}
opts.show_no_output = {}
sa.run_range(line,line, ft, opts)
end

function M.enable()
vim.cmd [[
augroup _sniprunlive
autocmd!
autocmd TextChanged * lua require'sniprun.live_mode'.run()
autocmd TextChangedI * lua require'sniprun.live_mode'.run()
augroup end
lua require'sniprun.live_mode'.run()
]]
vim.notify "Enabled Sniprun live mode"
end

function M.disable()
M.remove_augroup "_sniprunlive"
require('sniprun.display').clear_virtual_text()
vim.notify "Disabled Sniprun live mode"
end

function M.toggle()
if vim.fn.exists "#_sniprunlive#TextChanged" == 0 then
M.enable()
else
M.disable()
end
end

function M.remove_augroup(name)
if vim.fn.exists("#" .. name) == 1 then
vim.cmd("au! " .. name)
end
end

vim.cmd [[ command! SnipLive execute 'lua require("sniprun.live_mode").toggle()' ]]
vim.api.nvim_set_keymap("n", "<Plug>SnipLive", ":lua require'sniprun.live_mode'.toggle()<CR>",{silent=true})

return M
25 changes: 25 additions & 0 deletions ressources/live_mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# What is sniprun's live mode ?

The live mode hook the SnipRun command to the TextChanged event, meaning that at every change to make to the buffer, the current line will be sent to sniprun for evaluation. This can mean a lot of times, especially if you type fast.

The result is a virtual text, displaying at the end of the current line that print the result (stdout) of the line. Nothing is displayed when the line is incomplete / incorrect, a bit like codi.

# Warnings

The live mode **will execute code you didn't think really about** (and by that I mean even less than usual)
Thus:
- Your code will get executed **lots** of times; check that your CPU can keep up. Even a slow 60wpm typing can make a Rust program recompile 3x per second, which is also different from sending 3 string/s to a running REPL.
- Sniprun will try to execute even incomplete lines. You hadn't finished typing that `rm /path/to/arghhh` ? sniprun' not aware and removed the parent directory. Whoops. For these reasons, I strongly suggest to:
- never run bash/shell with live mode
- disable the live mode whenever your code modifies files or invoke system commands.

If you're running a REPL-capable interpreter, while it'll probably work, mind that:
- the REPL will have to gulp a lot of incomplete code without crashing and stuff
- typing b = b + 1 + 1 will increment b by more than 2 !! (since an intermediate b=b+1 is valid and thus changes b before b=b+1+1)

# Enable and usage

`live_mode_toggle='enable'` in the config, (set to either 'enable' or 'off' - the default -, the disreptancy is only to force people to come here and read the warnings in case some smart kid want to skip and just set it to 'on')

and then use the :SnipLive command and start coding.

14 changes: 13 additions & 1 deletion src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,23 @@ pub fn display_virtual_text(
data: &DataHolder,
is_ok: bool,
) {
let namespace_id = nvim.lock().unwrap().create_namespace("sniprun").unwrap();
if is_ok != result.is_ok() {
if let Err(SniprunError::InterpreterLimitationError(_)) = result {
return; // without clearing the line
}
// clear the current line
let last_line = data.range[1] - 1;
let _ = nvim.lock().unwrap().command(&format!(
"call nvim_buf_clear_namespace(0,{},{},{})",
namespace_id,
data.range[0] - 1,
last_line + 1
));

return; //don't display unasked-for things
}

let namespace_id = nvim.lock().unwrap().create_namespace("sniprun").unwrap();
info!("namespace_id = {:?}", namespace_id);

let last_line = data.range[1] - 1;
Expand Down
1 change: 1 addition & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum SniprunError {
FetchCodeError,

///when the user's code run into problems because of an interpreter's implementation
/// It's also the only Err that won't clear the virtual text if VirtualTextOk is selected
#[error("Interpreter limitation error: {0}")]
InterpreterLimitationError(String),

Expand Down
2 changes: 1 addition & 1 deletion src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ impl<T: Interpreter> InterpreterUtils for T {
.collect::<String>()
.split_whitespace()
.collect();
return compact_snippet.contains(&compact_main);
compact_snippet.contains(&compact_main)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/interpreters/C_original.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ impl Interpreter for C_original {
fn add_boilerplate(&mut self) -> Result<(), SniprunError> {
self.fetch_imports()?;

if !C_original::contains_main(&"int main (", &self.code, &"//") {
if !C_original::contains_main("int main (", &self.code, "//") {
self.code = String::from("int main() {\n") + &self.code + "\nreturn 0;}";
}
if !self.imports.iter().any(|s| s.contains("<stdio.h>")) {
Expand Down
2 changes: 1 addition & 1 deletion src/interpreters/Cpp_original.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ impl Interpreter for Cpp_original {
fn add_boilerplate(&mut self) -> Result<(), SniprunError> {
self.fetch_imports()?;

if !Cpp_original::contains_main(&"int main (", &self.code, &"//") {
if !Cpp_original::contains_main("int main (", &self.code, "//") {
self.code = String::from("int main() {\n") + &self.code + "\nreturn 0;}";
}
if !self.imports.iter().any(|s| s.contains("<iostream>")) {
Expand Down
6 changes: 3 additions & 3 deletions src/interpreters/Go_original.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@ impl Interpreter for Go_original {

fn add_boilerplate(&mut self) -> Result<(), SniprunError> {

if !Go_original::contains_main(&"func main (", &self.code, &"//") {
if !Go_original::contains_main("func main (", &self.code, "//") {
self.code = String::from("func main() {") + &self.code + "}";
}

if !Go_original::contains_main(&"import \"fmt\"", &self.code, &"//") {
if !Go_original::contains_main("import \"fmt\"", &self.code, "//") {
self.code = String::from("import \"fmt\"\n") + &self.code;
}

if !Go_original::contains_main(&"package main", &self.code, &"//") {
if !Go_original::contains_main("package main", &self.code, "//") {
self.code = String::from("package main\n") + &self.code;
}

Expand Down
2 changes: 1 addition & 1 deletion src/interpreters/Java_original.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl Interpreter for Java_original {

fn add_boilerplate(&mut self) -> Result<(), SniprunError> {

if !Java_original::contains_main(&"public static void main(", &self.code, &"//") {
if !Java_original::contains_main("public static void main(", &self.code, "//") {
self.code = String::from(
"public class Main {
public static void main(String[] args) {
Expand Down
8 changes: 5 additions & 3 deletions src/interpreters/Julia_original.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ impl Julia_original {
);

let mut contents = String::new();


let mut pause = std::time::Duration::from_millis(50);
loop {
std::thread::sleep(pause);
pause = pause.saturating_add(std::time::Duration::from_millis(50));

if let Ok(mut file) = std::fs::File::open(&path) {
info!("file exists");
let res = file.read_to_string(&mut contents);
Expand All @@ -40,8 +44,6 @@ impl Julia_original {
}
info!("not found yet");

let pause = std::time::Duration::from_millis(50);
std::thread::sleep(pause);
}

let index = contents.rfind(&start_mark).unwrap();
Expand Down
6 changes: 4 additions & 2 deletions src/interpreters/Mathematica_original.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ impl Mathematica_original {

let mut 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));
if let Ok(mut file) = std::fs::File::open(&path) {
info!("file exists");
let res = file.read_to_string(&mut contents);
Expand All @@ -75,8 +79,6 @@ impl Mathematica_original {
}
info!("not found yet");

let pause = std::time::Duration::from_millis(50);
std::thread::sleep(pause);
}

let index = contents.rfind(&start_mark).unwrap();
Expand Down
Loading

0 comments on commit e201601

Please sign in to comment.