├── .codecov.yml ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support-for--language-.md └── workflows │ ├── doc.yml │ ├── release.yml │ └── rust.yml ├── .gitignore ├── .gitmodules ├── API.md ├── CHANGELOG.md ├── CHECKLIST.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── autoload └── health │ └── sniprun.vim ├── build.rs ├── doc ├── Makefile ├── build.sh ├── conf.py ├── index.rst ├── sniprun.txt └── sources │ ├── README.md │ ├── common_options.md │ ├── display_modes.md │ └── interpreters │ ├── Ada_original.md │ ├── Bash_original.md │ ├── CS_original.md │ ├── CSharp_original.md │ ├── C_original.md │ ├── Clojure_fifo.md │ ├── Cpp_original.md │ ├── D_original.md │ ├── Elixir_original.md │ ├── FSharp_fifo.md │ ├── GFM_original.md │ ├── Generic.md │ ├── Go_original.md │ ├── Haskell.md │ ├── Http_original.md │ ├── JS_TS_bun.md │ ├── JS_TS_deno.md │ ├── Java_original.md │ ├── Julia_jupyter.md │ ├── Julia_original.md │ ├── Lua_nvim.md │ ├── Lua_original.md │ ├── Mathematica_original.md │ ├── Neorg_original.md │ ├── OCaml_fifo.md │ ├── OrgMode_original.md │ ├── Plantuml_original.md │ ├── Prolog_original.md │ ├── Python3_fifo.md │ ├── Python3_jupyter.md │ ├── Python3_original.md │ ├── R_original.md │ ├── Ruby_original.md │ ├── Rust_original.md │ ├── SQL_original.md │ ├── Sage_fifo.md │ ├── Scala_original.md │ └── TypeScript_original.md ├── install.sh ├── lua ├── sniprun.lua └── sniprun │ ├── api.lua │ ├── display.lua │ ├── input.lua │ └── live_mode.lua ├── plugin └── sniprun.vim ├── ressources ├── CONTRIBUTING_REPL.md ├── api.md ├── asciiart.txt ├── display_classic.md ├── display_floating_window.md ├── display_notify.md ├── display_terminal.md ├── display_virtualtext.md ├── gitscript.sh ├── go_install.sh ├── init_repl.sh ├── install_all_compilers_ci.sh ├── launcher_repl.sh ├── sync_repl.sh └── visual_assets │ ├── 760091.png │ ├── Sniprun_social_preview.png │ ├── Sniprun_transparent.png │ ├── api.png │ ├── classic.png │ ├── demo_c.gif │ ├── demo_repl.png │ ├── demo_rust.gif │ ├── display_virtline.png │ ├── error_example.png │ ├── favicon.ico │ ├── floating_window.png │ ├── logo-only.png │ ├── logo-only.xcf │ ├── logo-transparent.xcf │ ├── logo.xcf │ ├── nvimnotify.png │ ├── rust_error.png │ ├── terminal.png │ ├── terminalWithCode.png │ └── virtual_text.png ├── src ├── daemonizer.rs ├── display.rs ├── error.rs ├── input.rs ├── interpreter.rs ├── interpreters │ ├── Ada_original.rs │ ├── Bash_original.rs │ ├── CS_original.rs │ ├── CSharp_original.rs │ ├── C_original.rs │ ├── Clojure_fifo.rs │ ├── Cpp_original.rs │ ├── D_original.rs │ ├── Elixir_original.rs │ ├── FSharp_fifo.rs │ ├── GFM_original.rs │ ├── Generic.rs │ ├── Go_original.rs │ ├── Haskell_original.rs │ ├── Http_original.rs │ ├── JS_TS_bun.rs │ ├── JS_TS_deno.rs │ ├── JS_original.rs │ ├── Java_original.rs │ ├── Julia_jupyter.rs │ ├── Julia_original.rs │ ├── Julia_original │ │ └── init_repl.sh │ ├── Lua_nvim.rs │ ├── Lua_original.rs │ ├── Mathematica_original.rs │ ├── Mathematica_original │ │ ├── init_repl.sh │ │ └── launcher.sh │ ├── Neorg_original.rs │ ├── OCaml_fifo.rs │ ├── OrgMode_original.rs │ ├── Plantuml_original.rs │ ├── Prolog_original.rs │ ├── Python3_fifo.rs │ ├── Python3_jupyter.rs │ ├── Python3_original.rs │ ├── Python3_original │ │ └── saveload.py │ ├── R_original.rs │ ├── Ruby_original.rs │ ├── Rust_original.rs │ ├── SQL_original.rs │ ├── Sage_fifo.rs │ ├── Scala_original.rs │ ├── Swift_original.rs │ ├── TypeScript_original.rs │ ├── example.rs │ └── import.rs ├── launcher.rs ├── lib.rs └── main.rs └── tests └── integration.rs /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: 4 | default: 5 | target: auto 6 | threshold: 5 7 | path: "src" 8 | 9 | project: 10 | default: 11 | target: 50 12 | path: "src" 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # see https://github.com/CppCXY/EmmyLuaCodeStyle 3 | [*.lua] 4 | # [basic] 5 | 6 | # optional space/tab 7 | indent_style = space 8 | # if indent_style is space, this is valid 9 | indent_size = 4 10 | # if indent_style is tab, this is valid 11 | tab_width = 4 12 | # none/single/double 13 | quote_style = none 14 | 15 | continuation_indent = 4 16 | ## extend option 17 | # continuation_indent.before_block = 4 18 | # continuation_indent.in_expr = 4 19 | # continuation_indent.in_table = 4 20 | 21 | # this mean utf8 length , if this is 'unset' then the line width is no longer checked 22 | # this option decides when to chopdown the code 23 | max_line_length = 120 24 | 25 | # optional crlf/lf/cr/auto, if it is 'auto', in windows it is crlf other platforms are lf 26 | # in neovim the value 'auto' is not a valid option, please use 'unset' 27 | end_of_line = unset 28 | 29 | # none/ comma / semicolon / only_kv_colon 30 | table_separator_style = none 31 | 32 | #optional keep/never/always/smart 33 | trailing_table_separator = keep 34 | 35 | # keep/remove/remove_table_only/remove_string_only 36 | call_arg_parentheses = keep 37 | 38 | detect_end_of_line = false 39 | 40 | # this will check text end with new line 41 | insert_final_newline = true 42 | 43 | # [space] 44 | space_around_table_field_list = true 45 | 46 | space_before_attribute = true 47 | 48 | space_before_function_open_parenthesis = false 49 | 50 | space_before_function_call_open_parenthesis = false 51 | 52 | space_before_closure_open_parenthesis = true 53 | 54 | # optional always/only_string/only_table/none 55 | # or true/false 56 | space_before_function_call_single_arg = always 57 | ## extend option 58 | ## always/keep/none 59 | # space_before_function_call_single_arg.table = always 60 | ## always/keep/none 61 | # space_before_function_call_single_arg.string = always 62 | 63 | space_before_open_square_bracket = false 64 | 65 | space_inside_function_call_parentheses = false 66 | 67 | space_inside_function_param_list_parentheses = false 68 | 69 | space_inside_square_brackets = false 70 | 71 | # like t[#t+1] = 1 72 | space_around_table_append_operator = false 73 | 74 | ignore_spaces_inside_function_call = false 75 | 76 | # detail number or 'keep' 77 | space_before_inline_comment = 1 78 | 79 | # convert '---' to '--- ' or '--' to '-- ' 80 | space_after_comment_dash = false 81 | 82 | # [operator space] 83 | space_around_math_operator = true 84 | # space_around_math_operator.exponent = false 85 | 86 | space_after_comma = true 87 | 88 | space_after_comma_in_for_statement = true 89 | 90 | # true/false or none/always/no_space_asym 91 | space_around_concat_operator = true 92 | 93 | space_around_logical_operator = true 94 | 95 | # true/false or none/always/no_space_asym 96 | space_around_assign_operator = true 97 | 98 | # [align] 99 | 100 | align_call_args = false 101 | 102 | align_function_params = true 103 | 104 | align_continuous_assign_statement = true 105 | 106 | align_continuous_rect_table_field = true 107 | 108 | align_continuous_line_space = 2 109 | 110 | align_if_branch = false 111 | 112 | # option none / always / contain_curly/ 113 | align_array_table = true 114 | 115 | align_continuous_similar_call_args = false 116 | 117 | align_continuous_inline_comment = true 118 | # option none / always / only_call_stmt 119 | align_chain_expr = none 120 | 121 | # [indent] 122 | 123 | never_indent_before_if_condition = false 124 | 125 | never_indent_comment_on_if_branch = false 126 | 127 | keep_indents_on_empty_lines = false 128 | 129 | allow_non_indented_comments = false 130 | # [line space] 131 | 132 | # The following configuration supports four expressions 133 | # keep 134 | # fixed(n) 135 | # min(n) 136 | # max(n) 137 | # for eg. min(2) 138 | 139 | line_space_after_if_statement = keep 140 | 141 | line_space_after_do_statement = keep 142 | 143 | line_space_after_while_statement = keep 144 | 145 | line_space_after_repeat_statement = keep 146 | 147 | line_space_after_for_statement = keep 148 | 149 | line_space_after_local_or_assign_statement = keep 150 | 151 | line_space_after_function_statement = fixed(2) 152 | 153 | line_space_after_expression_statement = keep 154 | 155 | line_space_after_comment = keep 156 | 157 | line_space_around_block = fixed(1) 158 | # [line break] 159 | break_all_list_when_line_exceed = false 160 | 161 | auto_collapse_lines = false 162 | 163 | break_before_braces = false 164 | 165 | # [preference] 166 | ignore_space_after_colon = false 167 | 168 | remove_call_expression_list_finish_comma = false 169 | # keep / always / same_line / replace_with_newline / never 170 | end_statement_with_semicolon = keep 171 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., ['michaelb'] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://paypal.me/michaelb4sniprun?locale.x=fr_FR'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Open a file containing '...' 16 | 2. Run the command '....' 17 | 3. See error '...' 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Environment:** 26 | - Neovim version [e.g. v0.6.1] 27 | - OS/distribution 28 | 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-for--language-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support for 3 | about: 'Adding an interpreter for ' 4 | title: '' 5 | labels: new-langage-support 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the language you want support for (compiler, specificities)** 11 | I'd like an interpreter for that uses [and that work with detected filetypes x,y and z]... 12 | 13 | **Support Level to achieve** 14 | X-level support is enough for me.... 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Doc 2 | 3 | env: 4 | GITHUB_ACTOR: michaelb 5 | GITHUB_REPOSITORY: michaelb/sniprun 6 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 7 | 8 | on: 9 | push: 10 | branches: [ master ] 11 | workflow_dispatch: 12 | 13 | 14 | jobs: 15 | 16 | build_sphinx_job: 17 | runs-on: ubuntu-latest 18 | container: debian:buster-slim 19 | 20 | steps: 21 | 22 | - name: Get prerequisites and clone repository 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | run: | 26 | set -x 27 | apt-get update 28 | apt-get install -y git 29 | git clone "https://token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" . 30 | shell: bash 31 | 32 | - name: Run build script for Sphinx pages 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | run: "doc/build.sh" 36 | shell: bash 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | RUSTFLAGS: -C target-feature=+crt-static 11 | 12 | 13 | jobs: 14 | release: 15 | name: Publish release 16 | runs-on: ubuntu-20.04 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | persist-credentials: false 22 | - uses: dtolnay/rust-toolchain@stable 23 | - name: build 24 | run: cargo build --target x86_64-unknown-linux-gnu --release --verbose && mv ./target/x86_64-unknown-linux-gnu/release/sniprun . 25 | 26 | - name: upload to releases 27 | uses: svenstaro/upload-release-action@v2 28 | with: 29 | repo_token: ${{ secrets.GITHUB_TOKEN }} 30 | file: sniprun 31 | tag: ${{github.ref}} 32 | overwrite: false 33 | body: "This release and associated artifacts were created by Github Action. See the changelog [here](https://github.com/michaelb/sniprun/blob/master/CHANGELOG.md)." 34 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master] 6 | pull_request: 7 | branches: [ master, dev] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci-safety: 12 | name: ci-safety 13 | runs-on: ubuntu-20.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | persist-credentials: false 18 | - uses: astral-sh/setup-uv@v4 19 | - name: run zizmor ci analysis tool 20 | run: uvx zizmor --format sarif . > results.sarif 21 | env: 22 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | - name: Upload SARIF file 24 | uses: github/codeql-action/upload-sarif@v3 25 | with: 26 | sarif_file: results.sarif 27 | category: zizmor 28 | 29 | build: 30 | name : build-latest-stable 31 | runs-on: ubuntu-20.04 32 | # version used in release process to guarantee old-GLIBC compatibility 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | with: 37 | persist-credentials: false 38 | - uses: dtolnay/rust-toolchain@stable 39 | - name: Build 40 | run: cargo build --verbose --release --locked --target x86_64-unknown-linux-gnu 41 | 42 | buildmsrv: 43 | name: build-msrv 44 | runs-on: ubuntu-20.04 45 | steps: 46 | - uses: actions/checkout@v4 47 | with: 48 | persist-credentials: false 49 | - uses: dtolnay/rust-toolchain@1.65 50 | - name: build 51 | run: cargo build --release --locked 52 | 53 | buildnightly: 54 | name: build-nightly 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v4 58 | with: 59 | persist-credentials: false 60 | - uses: dtolnay/rust-toolchain@nightly 61 | - name: build 62 | run: cargo build --release --locked --target x86_64-unknown-linux-gnu 63 | 64 | unittest: 65 | name: unit & integration tests 66 | runs-on: ubuntu-20.04 67 | steps: 68 | - uses: actions/checkout@v4 69 | with: 70 | persist-credentials: false 71 | - uses: dtolnay/rust-toolchain@stable 72 | - run: './ressources/install_all_compilers_ci.sh' 73 | - uses: oven-sh/setup-bun@v1 74 | - uses: dlang-community/setup-dlang@v1 75 | - uses: erlef/setup-beam@v1 76 | with: 77 | otp-version: "23" 78 | elixir-version: "1.14" 79 | - name: Unit tests 80 | run: cargo test --release --features ignore_in_ci 81 | - name: integration tests 82 | run: cargo test --release --features ignore_in_ci --test integration 83 | 84 | install: 85 | name: install (download) 86 | runs-on: ubuntu-20.04 87 | steps: 88 | - uses: actions/checkout@v4 89 | with: 90 | persist-credentials: false 91 | - name: installation tests 92 | run: ./install.sh 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/interpreters/mod.rs 2 | test/ 3 | download_dir/ 4 | target/ 5 | ressources/infofile.txt 6 | test_sniprun.log 7 | default.profraw 8 | doc/_build 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/.gitmodules -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # Lua API to sniprun 2 | 3 | You can use sniprun API from: 4 | 5 | ```lua 6 | 7 | local sa = require('sniprun.api') 8 | 9 | ``` 10 | 11 | then, some functions are accessible, such as 12 | 13 | ``` 14 | sa.run_range(r_start, r_end, , ) 15 | sa.run_string(codestring, , ) 16 | 17 | ``` 18 | 19 | ranges are integers matching the (inclusive) line numbers 20 | 21 | codestring must be a string 22 | 23 | filetype (optional) must be a string such as 'python' 24 | 25 | config (optional) allows to override the default/user config. It's particularly interesting to provide the display type 'Api' in this field if you wish to retrieve sniprun's output without interference on the user UI. 26 | 27 | 28 | You can register listeners that will be called upon (async) sniprun output: 29 | 30 | 31 | ``` 32 | sa.register_listener(custom_function) 33 | ``` 34 | 35 | where custom function is a function that take one unique argument: a table which contains at least two entries: 36 | 37 | - 'status' (a string that's either 'ok' or 'error' for now, but your function should accept & manage other values) 38 | - 'message' (also a string, maybe be mutliline) 39 | 40 | (Simply put, registered functions are callbacks) 41 | 42 | 43 | 44 | ​ 45 | ​ 46 | 47 | Thus, an example of such a function (imitating the 'Classic' display with 'uwu' tendencies) would be 48 | 49 | ``` 50 | local api_listener = function (d) 51 | if d.status == 'ok' then 52 | print("Nice uwu: ", d.message) 53 | elseif d.status == 'error' then 54 | print("Oh nyow! Somethuwuing went wyong: ", d.message) 55 | else 56 | print("Whut is this myeow? I don't knyow this status type nyah") 57 | end 58 | end 59 | 60 | sa.register_listener(api_listener) 61 | ``` 62 | 63 | (You must also enable the 'Api' display option, and in this particular case where things are printed to the command line area, disabling 'Classic' is recommended) 64 | 65 | ​ 66 | 67 | If your function requires to be manually closed (on `SnipClose`), you can register a closer the same way: 68 | 69 | ``` 70 | sa.register_closer(custom_function) 71 | ``` 72 | 73 | 74 | ## Warnings 75 | 76 | Beware, sniprun is still thightly coupled to the current nvim buffer & instance, but there should not be dependencies for non-REPL, and interpreters running under Bloc-Level. 77 | 78 | REPL-capable and Import level (or more) interpreter may fetch information from the current buffer 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # External contributions 2 | - only accept contributions to 'dev' branch OR release immediately after 3 | (doc contribution can be exempted) 4 | 5 | # Prepare the release 6 | 7 | ## on dev branch 8 | - check compilation success 9 | - cargo fmt --all / cargo check / cargo clippy 10 | - update the changelog 11 | - remove the 'beta' from the version field in Cargo.toml 12 | - `cargo update --offline` # update sniprun's version in committed Cargo.lock 13 | 14 | ## Merge process 15 | - create a PR dev -> master 16 | - merge 17 | 18 | ## Post-merge (tag creation and push MUST be done one after the other!! DON'T add a commit in the meantime) 19 | 20 | - git pull the changes in master 21 | - create a new SIGNED tag vX.Y.Z on master: `git tag -s v8.9.5` (tag message should be equal to tag number, eg: v8.9.5) 22 | - verify the signed tag: `git tag -v v8.9.5` 23 | - git push origin vX.Y.Z 24 | 25 | - merge master in dev 26 | - Bump Cargo.toml to next version on dev, suffixed by 'beta' 27 | 28 | # When the tag gets pushed, the 'release' github action will automatically add the new tag to GitHub's 'Releases' 29 | 30 | # Check the release 31 | 32 | - Check CI status 33 | - Check Releases status 34 | - Edit release description if necessary 35 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sniprun" 3 | version = "1.3.18" 4 | authors = ["michaelb "] 5 | rust-version = "1.65" 6 | edition = "2018" 7 | 8 | [features] 9 | ignore_in_ci = [] 10 | 11 | [profile.release] 12 | strip = true 13 | 14 | [build-dependencies] 15 | # cc="*" 16 | 17 | [dependencies] 18 | neovim-lib = "0.6.1" 19 | log = "0.4.18" 20 | log-panics = { version = "2.1.0", features = ["with-backtrace"] } 21 | simple-logging = "2.0.2" 22 | close_fds = "0.3.2" 23 | thiserror = "1.0.69" 24 | dirs = "5.0.1" 25 | regex = "1.11.1" # up-to-date-regex needs a more recent Rust version 26 | strip-ansi-escapes = "0.2.0" 27 | libc = "0.2.79" 28 | serial_test = "2.0.0" 29 | 30 | # jupyter-client = { git = "https://gitlab.com/srwalker101/rust-jupyter-client.git", branch = "dev" } 31 | 32 | rmp = "=0.8.8" # fix rmp version because it breaks MSRV 33 | 34 | # Python3 interpreter 35 | unindent = "0.2.3" 36 | 37 | # Http interpreter 38 | ureq = { version = "=2.9.0", features = ["json", "native-certs"] } 39 | http-rest-file = "=0.5.0" 40 | url = "=2.4.0" 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael Bleuez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 18 | 19 | # Introduction 20 | 21 | Sniprun is a code runner plugin for neovim written in Lua and Rust. 22 | It aims to provide stupidly fast partial code testing for interpreted 23 | **and compiled** [languages](https://michaelb.github.io/sniprun/sources/README.html#support-levels-and-languages) 24 | . Sniprun blurs the line between standard save/run workflow, jupyter-like 25 | notebook, and REPL/interpreters. 26 | 27 |
28 | 29 | TLDR: `Plug 'michaelb/sniprun', {'do': 'sh install.sh'}`, `:SnipRun`, `:'<,'>SnipRun`, `:SnipInfo` 30 | 31 | # Installation, configuration, ... 32 | 33 | See [installation instructions](https://michaelb.github.io/sniprun/sources/README.html#installation) 34 | , [configuration tips](https://michaelb.github.io/sniprun/sources/README.html#configuration) 35 | , [usage explanations](https://michaelb.github.io/sniprun/sources/README.html#usage) 36 | and much more useful information on the 37 | [WIKI](https://michaelb.github.io/sniprun/). 38 | 39 | ![spacer](ressources/visual_assets/760091.png) 40 | 41 | ## Demos 42 | 43 | ##### Send to Sniprun snippets of any language 44 | A very simple example (in C), play the .gif and look in the command area: 45 | 46 | ![demo_c](ressources/visual_assets/demo_c.gif) 47 | 48 | ##### The result can be returned in multiple (even at the same time) ways: 49 | 50 | [Classic](ressources/display_classic.md)| [Virtual Text/Virtual Line](ressources/display_virtualtext.md) 51 | :------------------------------------------:|:------------------: 52 | ![](ressources/visual_assets/classic.png) | ![](ressources/visual_assets/virtual_text.png) 53 | [**Temporary Floating Window**](ressources/display_floating_window.md) | [**Terminal**](ressources/display_terminal.md) 54 | ![](ressources/visual_assets/floating_window.png) | ![](ressources/visual_assets/terminal.png) 55 | [**Notification**](ressources/display_notify.md) | [**API**](API.md) 56 | ![](ressources/visual_assets/nvimnotify.png) | ![](ressources/visual_assets/api.png) 57 | 58 | 59 | ##### REPL-like behavior is available for some languages 60 | 61 | Python, Julia, Lua, JavaScript & Typescript (via deno), Clojure, R, Mathematica, 62 | Sage, coming soon for many other interpreted (and compiled) languages. 63 | With [REPL-like behavior](https://michaelb.github.io/sniprun/sources/README.html#repl-like-behavior) 64 | ,you can run code dependent on previously executed code, just like in 65 | a REPL, from within your favorite editor. 66 | 67 | ![spacer](ressources/visual_assets/760091.png) 68 | 69 | ## Features 70 | 71 | **Sniprun is** a way to quickly run small snippets of code, on the fly, and 72 | iterate very quickly and conveniently. To learn a language, to quickly 73 | experiment with new features (not yet embedded in classes or a whole project 74 | etc...), or to develop simple code pipelines (like a machine learning exercise) 75 | that fit in a unique file, sniprun is probably _the_ best plugin out there. 76 | 77 | As a matter of proof, Sniprun : 78 | 79 | - Officially supports [all these languages (compiled & interpreted)](https://michaelb.github.io/sniprun/sources/README.html#support-levels-and-languages), 80 | and virtually [any language](https://michaelb.github.io/sniprun/sources/interpreters/Generic.html#community-examples-for-non-officially-supported-languages) 81 | - can create and connect to REPLs in order to present an interactive and 82 | playful interface 83 | - can run things like GUI plots, networks requests or even Ansible playbooks 84 | - doesn't require even one line of configuration by default 85 | (but can be customized up to the tiniest things) 86 | - can run code from a part of a file which isn't complete / contains errors 87 | - can automatically fetch (in some languages) the `imports` 88 | necessary for your code snippet 89 | - can run [live](https://michaelb.github.io/sniprun/sources/README.html#live-mode) 90 | (at every keystroke) 91 | - lends itself to easy [mappings](https://michaelb.github.io/sniprun/sources/README.html#mappings-recommandations) 92 | and Vim motions 93 | - has an API (for running code, and displaying results) 94 | - has many result display modes that can be enabled at the same time, 95 | and for different output status if wanted 96 | - supports literate programming in Markdown, Orgmode and Neorg 97 | 98 | ## Known limitations 99 | 100 | Due to its nature, Sniprun may have trouble with programs that : 101 | 102 | - Mess with standard output / stderr 103 | - Need to read from stdin 104 | - Access files; sniprun does not run in a virtual environment, it accesses 105 | files just like your own code do, but since it does not run the whole program 106 | , something might go wrong. **Relative paths may cause issues**, as the 107 | current working directory for sniprun will be somewhere in ~/.cache/sniprun, 108 | and relative imports may miss. 109 | - No support for Windows 110 | - NixOS, MacOS users have to compile sniprun locally. Sniprun has not been 111 | tested on other Unixes (besides Linux itself, of course) 112 | 113 | ## Changelog 114 | 115 | It's been quite a journey already! For history fans, see the [full changelog](CHANGELOG.md). 116 | 117 | ## Contributing 118 | 119 | Sniprun has been made contributor-friendly (see 120 | [CONTRIBUTING.md](CONTRIBUTING.md)), so it's relatively easy to create / fix 121 | interpreters for any language. But any (constructive) issue, discussion, or 122 | doc Pull Request is a welcome form of contribution ! 123 | -------------------------------------------------------------------------------- /autoload/health/sniprun.vim: -------------------------------------------------------------------------------- 1 | let s:scriptdir = expand(':p:h')."/../.." "root of the project 2 | let s:bin= s:scriptdir.'/target/release/sniprun' 3 | 4 | function health#sniprun#check() 5 | lua require'sniprun'.health() 6 | endfunction 7 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::Path; 3 | 4 | // fn build_tree_sitter(language_name: &str) { 5 | // let dir: PathBuf = ["ressources", language_name, "src"].iter().collect(); 6 | // 7 | // cc::Build::new() 8 | // .include(&dir) 9 | // .file(dir.join("parser.c")) 10 | // .file(dir.join("scanner.c")) 11 | // .compile(language_name); 12 | // } 13 | 14 | fn main() -> Result<(), std::io::Error> { 15 | // build_tree_sitter("tree-sitter-rust"); 16 | 17 | //clarify this 18 | let out_dir = "src/interpreters"; 19 | let dest_path = Path::new(&out_dir).join("mod.rs"); 20 | 21 | let mut string_to_write = "".to_string(); 22 | 23 | string_to_write.push_str("#![allow(non_snake_case)]\n"); 24 | string_to_write.push_str("pub mod import;\n"); 25 | 26 | for path in fs::read_dir(out_dir).unwrap() { 27 | let plugin = path.unwrap().file_name().into_string().unwrap(); 28 | if plugin == "mod.rs" || plugin == "example.rs" || plugin == "import.rs" { 29 | continue; 30 | } 31 | if !plugin.ends_with(".rs") { 32 | // not a rust file 33 | continue; 34 | } 35 | 36 | let plugin = plugin[..plugin.len() - 3].to_string(); 37 | string_to_write.push_str(&format!( 38 | "pub mod {}; 39 | pub use {}::{} as {}; 40 | ", 41 | plugin.clone(), 42 | plugin.clone(), 43 | plugin.clone(), 44 | plugin + "_type", 45 | )); 46 | } 47 | 48 | string_to_write.push_str( 49 | "#[macro_export] 50 | macro_rules! iter_types { 51 | ($($code:tt)*) => { 52 | ", 53 | ); 54 | 55 | for path in fs::read_dir(out_dir).unwrap() { 56 | let mut plugin = path.unwrap().file_name().into_string().unwrap(); 57 | if plugin == "mod.rs" || plugin == "import.rs" || plugin == "example.rs" { 58 | continue; 59 | } 60 | if !plugin.ends_with(".rs") { 61 | // not a rust file 62 | continue; 63 | } 64 | plugin = plugin[..plugin.len() - 3].to_string(); 65 | 66 | string_to_write.push('{'); 67 | string_to_write.push_str(&format!( 68 | " 69 | type Current = interpreters::{}_type; 70 | $( 71 | $code 72 | )* 73 | ", 74 | plugin, 75 | )); 76 | string_to_write.push_str("};"); 77 | } 78 | string_to_write.push_str( 79 | " 80 | }; 81 | } 82 | ", 83 | ); 84 | 85 | // cargo stuff for rebuild 86 | 87 | for path in fs::read_dir(out_dir).unwrap() { 88 | let _plugin_path = path.unwrap().path().display(); 89 | } 90 | println!( 91 | "cargo:rerun-if-changed=build.rs 92 | " 93 | ); 94 | println!( 95 | "cargo:rerun-if-changed={} 96 | ", 97 | out_dir 98 | ); 99 | for path in fs::read_dir(out_dir).unwrap() { 100 | println!( 101 | "cargo:rerun-if-changed={} 102 | ", 103 | path.unwrap().path().display() 104 | ); 105 | } 106 | 107 | fs::write(dest_path, string_to_write) 108 | } 109 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /doc/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | set -e 4 | 5 | apt-get update 6 | apt-get -y install git rsync python3-pip python3-venv 7 | 8 | python3 -m venv venv 9 | source venv/bin/activate 10 | pip install myst-parser==1.0.0 docutils==0.18.1 sphinx-rtd-theme==1.2.0 sphinx==5.0 11 | 12 | pwd ls -lah 13 | git config --global --add safe.directory /__w/sniprun/sniprun 14 | export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) 15 | 16 | ############## 17 | # BUILD DOCS # 18 | ############## 19 | # 20 | 21 | 22 | # Python Sphinx, configured with source/conf.py 23 | # See https://www.sphinx-doc.org/ 24 | cd doc 25 | make clean 26 | make html 27 | 28 | deactivate 29 | 30 | ####################### 31 | # Update GitHub Pages # 32 | ####################### 33 | 34 | git config --global user.name "${GITHUB_ACTOR}" 35 | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" 36 | 37 | docroot=`mktemp -d` 38 | rsync -av "_build/html/" "${docroot}/" 39 | 40 | pushd "${docroot}" 41 | 42 | git init 43 | git remote add deploy "https://token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" 44 | git checkout -b gh-pages 45 | 46 | # Adds .nojekyll file to the root to signal to GitHub that 47 | # directories that start with an underscore (_) can remain 48 | touch .nojekyll 49 | 50 | 51 | # Add README 52 | cat > README.md < 6 | Vigouroux Thomas 7 | Dingcheng Yue 8 | https://github.com/michaelb/sniprun/graphs/contributors 9 | 10 | 11 | ============================================================================== 12 | INTRODUCTION *sniprun-intro* 13 | 14 | Sniprun is a code runner plugin that run, and if necessary, compile beforehand, snippets (visual selection) 15 | of your code. 16 | 17 | 18 | ============================================================================== 19 | QUICK START *sniprun-quickstart* 20 | 21 | 22 | 23 | :'<'>Sniprun Send current visual selection (line-wise) to Sniprun 24 | :SnipRun Send the current line to Sniprun 25 | 26 | 27 | :SnipReset Restart sniprun (that will also clear the cache that helps Sniprun compile faster) 28 | :SnipInfo Get info about sniprun and the available / current interpreter 29 | 30 | 31 | ============================================================================== 32 | ALL COMMANDS *sniprun-commands* 33 | 34 | :SnipRun Send the current line to Sniprun 35 | :'<'>Sniprun Send current visual selection (line-wise) to Sniprun 36 | 37 | :SnipReset Restart sniprun / reset from a clean slate 38 | 39 | :SnipReplMemoryClean If you sent incorrect code to a interpreter with enabled REPL mode, you can clear the 40 | REPL 'memory' with that. It will forget all variables you have defined previously etc.. 41 | 42 | :SnipInfo Get Sniprun version info and info about the available interpreters 43 | 44 | :SnipClose Clear virtual text and close splits and floating windows created by sniprun 45 | 46 | :SnipLive Toggle live mode (read the docs on github.com/michaelb/sniprun first !) 47 | This command is not available by default given how much important knowledge about that is 48 | 49 | 50 | ============================================================================== 51 | CONFIGURATION *sniprun-configuration* 52 | 53 | Add the setup() call somewhere in your config files and modify the fields as needed, relevant info can be found with :SnipInfo 54 | 55 | Note: the 'lua << EOF .... EOF' part is only needed if you configure sniprun in a vimscript (.vim) file. If you configure sniprun in a lua file (.lua), directly 'require' sniprun. 56 | 57 | You can omit any configuration fields, the defaults will be used instead; so the most basic sniprun config is just: 58 | 59 | lua < 75 | 76 | --# use the interpreter name as key 77 | GFM_original = { 78 | use_on_filetypes = {"markdown.pandoc"} --# the 'use_on_filetypes' configuration key is 79 | --# available for every interpreter 80 | }, 81 | Python3_original = { 82 | error_truncate = "auto" --# Truncate runtime errors 'long', 'short' or 'auto' 83 | --# the hint is available for every interpreter 84 | --# but may not be always respected 85 | } 86 | }, 87 | 88 | --# you can combo different display modes as desired and with the 'Ok' or 'Err' suffix 89 | --# to filter only sucessful runs (or errored-out runs respectively) 90 | display = { 91 | "Classic", --# display results in the command-line area 92 | "VirtualTextOk", --# display ok results as virtual text (multiline is shortened) 93 | 94 | -- "VirtualText", --# display results as virtual text 95 | -- "TempFloatingWindow", --# display results in a floating window 96 | -- "LongTempFloatingWindow", --# same as above, but only long results. To use with VirtualText[Ok/Err] 97 | -- "Terminal", --# display results in a vertical split 98 | -- "TerminalWithCode", --# display results and code history in a vertical split 99 | -- "NvimNotify", --# display with the nvim-notify plugin 100 | -- "Api" --# return output to a programming interface 101 | }, 102 | 103 | live_display = { "VirtualTextOk" }, --# display mode used in live_mode 104 | 105 | display_options = { 106 | terminal_scrollback = vim.o.scrollback, -- change terminal display scrollback lines 107 | terminal_line_number = false, -- whether show line number in terminal window 108 | terminal_signcolumn = false, -- whether show signcolumn in terminal window 109 | terminal_width = 45, --# change the terminal display option width 110 | notification_timeout = 5 --# timeout for nvim_notify output 111 | }, 112 | 113 | --# You can use the same keys to customize whether a sniprun producing 114 | --# no output should display nothing or '(no output)' 115 | show_no_output = { 116 | "Classic", 117 | "TempFloatingWindow", --# implies LongTempFloatingWindow, which has no effect on its own 118 | }, 119 | 120 | --# customize highlight groups (setting this overrides colorscheme) 121 | snipruncolors = { 122 | SniprunVirtualTextOk = {bg="#66eeff",fg="#000000",ctermbg="Cyan",cterfg="Black"}, 123 | SniprunFloatingWinOk = {fg="#66eeff",ctermfg="Cyan"}, 124 | SniprunVirtualTextErr = {bg="#881515",fg="#000000",ctermbg="DarkRed",cterfg="Black"}, 125 | SniprunFloatingWinErr = {fg="#881515",ctermfg="DarkRed"}, 126 | }, 127 | 128 | live_mode_toggle='off' --# live mode toggle, either 'off' or 'enable' 129 | 130 | --# miscellaneous compatibility/adjustement settings 131 | inline_messages = false, --# boolean toggle for a one-line way to display messages 132 | --# to workaround sniprun not being able to display anything 133 | 134 | borders = 'single', --# display borders around floating windows 135 | --# possible values are 'none', 'single', 'double', or 'shadow' 136 | }) 137 | EOF 138 | 139 | 140 | ============================================================================== 141 | TROUBLESHOOTING *sniprun-troubleshooting* 142 | 143 | If anything is not working; you should consult the README at https://michaelb.github.io/sniprun/index.html, 144 | as well as the documentation for every interpreter through :SnipInfo , and the :checkhealth sniprun 145 | -------------------------------------------------------------------------------- /doc/sources/common_options.md: -------------------------------------------------------------------------------- 1 | ## Interpreter configuration 2 | 3 | You can select an interpreter (with its name from `:SnipInfo`) if several of them support the language/filetype 4 | (see {ref}`use_on_filetype ` if necessary) and you want a specific one 5 | 6 | ``` 7 | lua << EOF 8 | require'sniprun'.setup({ 9 | selected_interpreters = { "Some_interpreter", "Python3_fifo"}, 10 | }) 11 | EOF 12 | ``` 13 | 14 | Many interpreters have REPL capabilities that are _not_ enabled by default. You can turn this behavior on (or off) with the `repl_enable` and `repl_disable` keys, that similarly take interpreters names as arguments: 15 | 16 | 17 | ``` 18 | lua << EOF 19 | require'sniprun'.setup({ 20 | repl_enable = {"Python3_original", "Julia_jupyter"}, --# enable REPL-like behavior for the given interpreters 21 | repl_disable = {"Lua_nvim"}, --# disable REPL-like behavior for the given interpreters 22 | }) 23 | EOF 24 | ``` 25 | 26 | 27 | ## Common options 28 | 29 | Every interpreter supports getting documentation via `:SnipInfo ` 30 | 31 | To specify interpreter options, you have to add the following to your sniprun config: 32 | 33 | 34 | 35 | ``` 36 | lua < = { 41 | some_specific_option = value, 42 | some_other_option = other_value, 43 | } 44 | }, 45 | -- ... 46 | }) 47 | EOF 48 | ``` 49 | 50 | For example: 51 | 52 | ``` 53 | lua < 57 | 58 | --# use the interpreter name as key 59 | GFM_original = { 60 | use_on_filetypes = {"markdown.pandoc"} --# the 'use_on_filetypes' configuration key is 61 | --# available for every interpreter 62 | }, 63 | Python3_original = { 64 | error_truncate = "auto" --# Truncate runtime errors 'long', 'short' or 'auto' 65 | --# the hint is available for every interpreter 66 | --# but may not be always respected 67 | } 68 | }, 69 | }) 70 | EOF 71 | ``` 72 | 73 | (use-on-filetype)= 74 | ### The "use_on_filetypes" key 75 | 76 | The `use_on_filetypes` key is implicitely an option of every interpreter 77 | 78 | ``` 79 | interpreter_options = { 80 | GFM_original = { 81 | use_on_filetypes = {"markdown.pandoc", "rstudio" } 82 | } 83 | } 84 | ``` 85 | 86 | ### The "repl_timeout" key 87 | 88 | REPL-enabled interpreters _sometime_ have mechanisms in place to limit how long a snippet of code 89 | can run. Be it because an infinite loop was (unintentionally?) run, or "something happened" which 90 | can make the interpreter unsuable in the meantime, limiting that 'meantime' is generally a good idea. 91 | 92 | By default, this timeout is set to 30s, after which, if no result was produced, the message: 93 | `Interpreter limitation: reached the repl timeout` is returned (as an error). 94 | 95 | Note that running an infinite loop may still cause the underlying REPL of the language (in the 96 | cases where one is used) to be stuck. The only purpose of this timeout is actually to warn the user 97 | something is taking 'probably' too long. This is why after getting such an error, it's better to 98 | run a `SnipReset` before trying to continue using the interpreter. 99 | 100 | This key is customizable per-interpreter, though only some (most) REPL-enabled interpreter will respect it: 101 | 102 | ```lua 103 | interpreter_options = { 104 | Python3_fifo = { 105 | repl_timeout = 900, -- 900s = 15min max runtime 106 | }, 107 | ``` 108 | 109 | 110 | ### The "error_truncate" key 111 | 112 | Also available for every interpreter if you don't like how sniprun truncate some outputs by default (auto), but it will not have an effect on all interpreters. 113 | 114 | ```lua 115 | interpreter_options = { 116 | Python3_original = { 117 | error_truncate = "auto" --# Truncate runtime errors 'long', 'short' or 'auto' 118 | } 119 | }, 120 | ``` 121 | 122 | ## The interpreter/compiler keys 123 | 124 | Almost every interpreter support either the "interpreter" or "compiler" key even if not explicitely documented, depending on whether they're about an interpreter or compiled language. 125 | 126 | example: 127 | 128 | ``` 129 | interpreter_options = { 130 | Python3_original = { 131 | interpreter = "python3.9" 132 | } 133 | Rust_original = { 134 | compiler = "/home/user/bin/rustc-patched -Zlocation-detail=none" 135 | } 136 | }, 137 | ``` 138 | 139 | You can see what interpreters/compilers are being used at any time by watching sniprun's log for the line 140 | "using compiler XXXX" or "using interpreter XXXX" when you run a snippet. 141 | While options can (generally) be added to these interpreters/compilers strings, mind that some options are often already passed, and 142 | sometimes mandatory (ex: "-o main_file_name", "--nologo") and whatever is added can mess up the format 143 | sniprun internally expect, or be straight out incompatible with the formers. Be careful! 144 | 145 | Exceptions: 146 | - Scala_original has both interpreter and compiler keys that should be set consistently with each other 147 | - *_jupyter, Generic, GFM_original, Orgmode_original, and Neorg_original do not support any of these keys, 148 | as they rely on the underlying interpreter for the code's block language and use its configuration. 149 | 150 | 151 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Ada_original.md: -------------------------------------------------------------------------------- 1 | ## Ada original 2 | 3 | dependencies: Need `gcc-ada` and `gnatmake` 4 | 5 | 6 | Note: because Ada needs variables to be declared before the begin 7 | (in a non contiguous section of the file), SnipRun is not very useful 8 | here and will, in practice, only be able to run blocs like 9 | 10 | ```ada 11 | Put_Line("raw text"); 12 | ``` 13 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Bash_original.md: -------------------------------------------------------------------------------- 1 | ## Bash original 2 | 3 | Beware of Bash_original, as it runs as script on your system, 4 | with access to your ENV variables and PATH etc... 5 | 6 | removing a file from absolute path will succeed! 7 | 8 | REPL mode has also many quirks (not a true repl, will rerun 9 | previously sniprun'd commands). Overall I strongly suggest _not_ using it 10 | -------------------------------------------------------------------------------- /doc/sources/interpreters/CS_original.md: -------------------------------------------------------------------------------- 1 | ## CS original (coffeescript) 2 | 3 | needs the `coffee` compiler on the PATH 4 | -------------------------------------------------------------------------------- /doc/sources/interpreters/CSharp_original.md: -------------------------------------------------------------------------------- 1 | ## C# original 2 | 3 | This interpreter require the `mono` toolbox to be installed: 4 | `csc` and `mono` must be in your $PATH 5 | 6 | a custom compiler can be specified : 7 | 8 | ```lua 9 | require'sniprun'.setup({ 10 | interpreter_options = { 11 | CSharp_original = { 12 | compiler = "csc" 13 | } 14 | } 15 | } 16 | }) 17 | ``` 18 | -------------------------------------------------------------------------------- /doc/sources/interpreters/C_original.md: -------------------------------------------------------------------------------- 1 | ## C original 2 | 3 | `gcc` is recommended, for that it's able to detect, compile and run nested 4 | functions, however you can change the default compiler with: 5 | 6 | ```lua 7 | require'sniprun'.setup({ 8 | interpreter_options = { 9 | C_original = { 10 | compiler = "clang" 11 | } 12 | } 13 | } 14 | }) 15 | ``` 16 | 17 | If you run with GCC, Sniprun will be able to run function + code in the 18 | same snippet, or functions + main() function regardlessly, but only the 19 | latter is supported by `clang`. 20 | 21 | This interpreter will also only import (all) #include \<...> but not 22 | any #include "..." (systems-wide include only, not the headers from your 23 | project, unless the environment variable `$C_INCLUDE_PATH` or 24 | `$CPLUS_INCLUDE_PATH` have been set). In this case, please make sure those 25 | variable cover **ALL** the paths needed to fetch every header file `#include`'d 26 | 27 | 28 | the C\_original interpreter will also make use of the following environment variables: 29 | 30 | - `$C_INCLUDE_PATH` 31 | - `$C_PLUS_INCLUDE_PATH` 32 | - `$LIBRARY_PATH` 33 | - `$CFLAGS` 34 | 35 | 36 | and will add them to the build options it uses. 37 | Please specify _absolute paths_, and not relative ones! 38 | 39 | 40 | Using a tool such as [direnv](https://direnv.net/) may be really useful 41 | to set up those variables when you `cd` into your project' directory. 42 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Clojure_fifo.md: -------------------------------------------------------------------------------- 1 | ## Clojure fifo 2 | 3 | This interpreter relies on `clojure` 4 | 5 | The default interpreter command is `clojure` 6 | (or `clojure -e "(clojure.main/repl :prompt (defn f[] ("")) )"`, that allow 7 | not displaying the repl prompt) but it can be changed via the configuration key 8 | 9 | The defaults are equivalent to specifying: 10 | 11 | ```lua 12 | require'sniprun'.setup({ 13 | interpreter_options = { 14 | Clojure_fifo = { 15 | interpreter_repl = "clojure -e \"(clojure.main/repl :prompt (defn f[] (\"\")) )\"" 16 | interpreter = "clojure" 17 | } 18 | } 19 | } 20 | }) 21 | ``` 22 | 23 | You can change those values, (to use `clj` for example ?) 24 | but it could break sniprun anytime 25 | 26 | ### Blocked REPL 27 | 28 | Clojure is a bit capricious and sometimes a typo will block forever 29 | (and a timeout will appear after 30s). 30 | Don't hesitate to `SnipReset`, even though it will lose all 31 | the currently in-memory variables... 32 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Cpp_original.md: -------------------------------------------------------------------------------- 1 | ## Cpp original 2 | 3 | Limitations 4 | 5 | - Will only look and load external imports (include) that are SYSTEM import; 6 | as sniprun does not have any way to differentiate between #include "math.h" 7 | (the system library) and #include "math2.h" (your custom header), 8 | it will NOT look for #include "....", but only #include \<....> 9 | (those are restricted to system libraries). 10 | 11 | - Need `g++` (or specify another compiler in configuration) 12 | 13 | ```lua 14 | require'sniprun'.setup({ 15 | interpreter_options = { 16 | Cpp_original = { 17 | compiler = "clang --debug" 18 | } 19 | } 20 | } 21 | }) 22 | ``` 23 | -------------------------------------------------------------------------------- /doc/sources/interpreters/D_original.md: -------------------------------------------------------------------------------- 1 | ## D original 2 | 3 | For the D language, needs the `dmd` executable 4 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Elixir_original.md: -------------------------------------------------------------------------------- 1 | ## Elixir original 2 | 3 | Needs `elixir` (or `iex` for REPL mode) available and in $PATH) 4 | -------------------------------------------------------------------------------- /doc/sources/interpreters/FSharp_fifo.md: -------------------------------------------------------------------------------- 1 | ## F# fifo 2 | 3 | ### This interpreter relies on dotnet fsi being available and on your path 4 | 5 | The default interpreter command is `dotnet fsi --nologo` but it 6 | can be changed via the configuration key 7 | 8 | ```lua 9 | require'sniprun'.setup({ 10 | interpreter_options = { 11 | FSharp_fifo = { 12 | interpreter = "...." 13 | } 14 | } 15 | } 16 | }) 17 | ``` 18 | 19 | ### REPL (would solve slowness issues) 20 | 21 | For now, REPL is broken due to dotnet fsi being capricious about its stdin. 22 | 23 | I'll explain rapidly how sniprun implements a REPL interpreter 24 | around named pipes (FIFOs). 25 | 26 | The first time a fifo-based interpreter receive a run command, 27 | it forks to the background and executes `ressources/init_repl.sh`. 28 | There is a lot of thing in that script but to replicate, you just have to: 29 | 30 | - `mkfifo pipe_in` 31 | 32 | - create a launcher script: 33 | 34 | ```bash 35 | #!/bin/bash 36 | cat pipe_in | dotnet fsi 37 | 38 | # or replace 'dotnet fsi' by whatever you cant to try 39 | ``` 40 | 41 | - launch it in the background: `bash ./launcher.sh &`, 42 | (or `bash ./launcher.sh > out.txt & ` to redirect stdout to out.txt like sniprun does) 43 | 44 | - ensure the pipe will stay open: `sleep 3600 > pipe_in &` 45 | (cat, exec 3> variations will also work) 46 | 47 | - `echo "printfn \" hey \" " > pipe_in` or `cat hello_world.fsx > pipe_in` 48 | 49 | - normally, the result should be printed in the terminal that ran 50 | the launcher, or in the out file. 51 | 52 | #### The issue: 53 | 54 | right now, dotnet fsi looks like it's blocked by the first sleep > pipe_in... 55 | but something **has** to keep the pipe open or when it closes, the fsi REPL 56 | reading from that will exit. 57 | 58 | I suspect the thing has something to do with interactive mode. 59 | 60 | For example, `python` has a similar problem, but `python -i ` 61 | (forced interactive mode, even if no terminal is detected because it 62 | runs in the background / its stdin was hijacked) works fine in the above example 63 | 64 | If you find something to replace dotnet fsi with, that exhibits the same 65 | correct behavior as `python -i`, sniprun REPL mode _should_ work. 66 | 67 | -------------------------------------------------------------------------------- /doc/sources/interpreters/GFM_original.md: -------------------------------------------------------------------------------- 1 | ## GFM original (Markdown) 2 | 3 | the GFM_original (Github flavored markdown) can help run code blocs embedded in markdown. 4 | 5 | ### example 1 6 | 7 | \```bash 8 | 9 | echo "lol" # << you can run sniprun on this line 10 | 11 | \# or the whole visual selection following: 12 | 13 | for i in {1..4};do 14 | 15 | echo $i 16 | 17 | done 18 | 19 | \``` 20 | 21 | 22 | ### example 2 23 | 24 | 25 | \```rust << running on this line will run the entire bloc 26 | 27 | println!("test"); 28 | 29 | \``` 30 | 31 | **the language name must be there (otherwise the default * will be used) at the 32 | bloc start** and has to match the github flavor syntax, and the underlying 33 | interpreter must be callable (no missing compiler etc...) 34 | 35 | \* python, but you can ofc configure that: 36 | 37 | ```lua 38 | require'sniprun'.setup({ 39 | interpreter_options = { 40 | GFM_original = { 41 | default_filetype = 'bash' -- default filetype (not 'github flavored markdown 'name' of the language, as they are sometimes different) 42 | } 43 | } 44 | }) 45 | ``` 46 | 47 | if GFM doesn't include a flavor that matches the language you want, well it's 48 | not really GFM anymore but you can but the filetype of the language you want, 49 | such as julia or lua 50 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Generic.md: -------------------------------------------------------------------------------- 1 | ## Generic 2 | 3 | This interpreter allows you to support virtually any language, 4 | provided it's not too strange, at up to bloc-level 5 | 6 | If you're trying to override an already-supported language, 7 | add Generic to the list of selected interpreters: 8 | 9 | 10 | ```lua 11 | require'sniprun'.setup({ 12 | selected_interpreters = { 'Generic' }, 13 | }) 14 | ``` 15 | to add support for, let's say, python2 16 | 17 | ```lua 18 | require'sniprun'.setup({ 19 | interpreter_options = { 20 | Generic = { 21 | error_truncate="long", -- strongly recommended to figure out what's going on 22 | MyPython2Config = { -- any key name is ok 23 | supported_filetypes = {"python2"}, -- mandatory 24 | extension = ".py", -- recommended, but not mandatory. Sniprun use this to create temporary files 25 | 26 | interpreter = "python2", -- interpreter or compiler (+ options if any) 27 | compiler = "", -- exactly one of those MUST be non-empty 28 | } 29 | } 30 | } 31 | } 32 | }) 33 | ``` 34 | 35 | to also add support for, let's suppose it wasn't officially supported, C 36 | 37 | ```lua 38 | require'sniprun'.setup({ 39 | interpreter_options = { 40 | Generic = { 41 | error_truncate="long", 42 | MyPython2Config = { 43 | supported_filetypes = {"python2"}, 44 | extension = ".py", 45 | 46 | interpreter = "python2", 47 | compiler = "", 48 | 49 | exe_name = "", 50 | boilerplate_pre = "" 51 | boilerplate_post = "" 52 | }, 53 | 54 | my_super_c_config = { 55 | supported_filetypes = {"c", "mysupercfiletype}, 56 | extension = ".c", 57 | 58 | interpreter = "", 59 | compiler = "gcc -o my_main -O3", -- compiler (+ options if necessary) (current working directory is sniprun's work directory - next to sniprun's log in $XDG_CACHE_DIR) 60 | 61 | exe_name = "my_main", -- executable name, by default a.out (always in sniprun's work directory) 62 | boilerplate_pre = "#include \nint main () {" -- include this before code snippets 63 | boilerplate_post = "}" -- include this after code snippets 64 | } 65 | } 66 | }, 67 | 68 | selected_interpreters = {"Generic"} 69 | -- other sniprun options ... 70 | }) 71 | ``` 72 | 73 | ### How the generic interpreter works 74 | 75 | #### For interpreted languages ("interpreter" is set) 76 | 77 | 1. Sniprun receive a snippet of code to run 78 | 2. The snippet gets surrounded by boilerplate_pre and boilerplate_post 79 | 3. The whole thing is written to a file with the given extension, named `_src.` 80 | 4. Sniprun runs ` .` and displays the stdout/stderr 81 | 82 | #### For compiled languages ("compiler" is set) 83 | 84 | 1. Sniprun receive a snippet of code to run 85 | 2. The snippet gets surrounded by boilerplate_pre and boilerplate_post 86 | 3. The whole thing is written to a temporary file with the given extension, 87 | named `_src.` 88 | 4. Sniprun runs ` _src.` , and if this has a 89 | non-success status, displays the stderr 90 | 5. Sniprun runs `./` and displays the stdout/stderr 91 | 92 | ### Community examples for non-officially supported languages 93 | 94 | (contribute on github to the file: doc/sources/interpreters/Generic.md) 95 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Go_original.md: -------------------------------------------------------------------------------- 1 | ## Go original 2 | the executable (`go` , `llgo` or whatever) executable used to 3 | _build_ the snippet can be configured via 4 | 5 | 6 | ```lua 7 | require'sniprun'.setup({ 8 | interpreter_options = { 9 | Go_original = { 10 | compiler = "gccgo" 11 | } 12 | } 13 | } 14 | }) 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Haskell.md: -------------------------------------------------------------------------------- 1 | ## Haskell original 2 | 3 | require the `ghc` compiler and the base libraries such as haskell-base-prelude 4 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Http_original.md: -------------------------------------------------------------------------------- 1 | ## Http original 2 | 3 | This interpreter does not have any requirements, as 4 | it relies on the (embedded) Rust library [ureq](https://crates.io/crates/ureq/) 5 | 6 | It works on filetypes 'http' and 'rest'. 7 | -------------------------------------------------------------------------------- /doc/sources/interpreters/JS_TS_bun.md: -------------------------------------------------------------------------------- 1 | ## JS_TS_bun 2 | 3 | A REPL-capable (not enabled by default) Typescript / Javascript interpreter. 4 | 5 | `bun` needs to be installed and on your path. 6 | 7 | ### JS_TS_bun is REPL-capable 8 | 9 | But the REPL is VERY quirky (and even has a greeting saying it's unstable) 10 | It doesn't play well at all with sniprun's stdin-stdout 11 | mechanism, so while basic examples are working, 12 | I can't consider this a 'daily driver'... so REPL is disabled by default 13 | 14 | ```lua 15 | require('sniprun').setup({ 16 | selected_interpreters={"JS_TS_bun"}, 17 | repl_enable={"JS_TS_bun"} 18 | }) 19 | ``` 20 | 21 | ### more option for the (non-repl) command line 22 | 23 | sniprun runs your code snippets with 24 | 25 | `bun run --silent ` 26 | 27 | more arguments for `bun run` can be added with the interpreter option: 28 | 29 | ```lua 30 | require'sniprun'.setup({ 31 | interpreter_options = { 32 | JS_TS_bun = { 33 | bun_run_opts = "--smol" 34 | } 35 | } 36 | } 37 | }) 38 | ``` 39 | -------------------------------------------------------------------------------- /doc/sources/interpreters/JS_TS_deno.md: -------------------------------------------------------------------------------- 1 | ## JS_TS_Deno 2 | 3 | A REPL-capable (not enabled by default) Typescript / Javascript interpreter. 4 | 5 | `deno` needs to be installed and on your path (and working). 6 | The precise command used by sniprun is `deno repl -q` 7 | 8 | ```lua 9 | require('sniprun').setup({ 10 | selected_interpreters={"JS_TS_deno"}, 11 | repl_enable={"JS_TS_deno"} 12 | }) 13 | ``` 14 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Java_original.md: -------------------------------------------------------------------------------- 1 | ## Java original 2 | 3 | no special conf is needed, besides having a functionnal 4 | `javac` compiler present on the $PATH 5 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Julia_jupyter.md: -------------------------------------------------------------------------------- 1 | ## Julia jupyter 2 | 3 | The setup for the julia_jupyter interpreter is quite convoluted: 4 | 5 | Indeed, the Julia jupyter kernel MUST be started before Sniprun can run 6 | (and there is even a consequent delay since the kernel is so slow to start). 7 | 8 | You should start a julia jupyter kernel with the following command: 9 | ` jupyter-kernel --kernel=julia-1.5 --KernelManager.connection_file=$HOME/.cache/sniprun/julia_jupyter/kernel_sniprun.json` 10 | 11 | (adapt to your XDG_CACHE location if you're on Mac) 12 | 13 | You manage kernel startup AND shutdown manually. Why? There is a terrible 14 | data race if sniprun does it. Python_jupyter works, julia doesn't. That's life. 15 | 16 | If you want to use another kernel location, post a feature request at 17 | github.com/michaelb/sniprun and I'll see what I can do. 18 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Julia_original.md: -------------------------------------------------------------------------------- 1 | ## Julia original 2 | 3 | Simply needs the `julia` executable 4 | 5 | REPL mode can be activated with this configuration: 6 | 7 | ```lua 8 | require'sniprun'.setup({ 9 | repl_enable = {'Julia_original'}, 10 | }) 11 | ``` 12 | 13 | Julia_original supports several interpreter options, such as the interpreter 14 | executable (absolute path or command in PATH, by default "julia"), and the 15 | 'project' (will be passed as --project=... to the executed command) 16 | 17 | ```lua 18 | require('sniprun').setup({ 19 | interpreter_options = { 20 | Julia_original = { 21 | project="." --# either a fixed absolute path, or "." for nvim's current directory (from echo getcwd() ) 22 | --# This directory has to contain a {Project,Manifest}.toml ! 23 | interpreter="/path/to/custom/julia" 24 | } 25 | } 26 | }) 27 | ``` 28 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Lua_nvim.md: -------------------------------------------------------------------------------- 1 | ## Lua_nvim 2 | 3 | This interpreter works inherently in a pseudo-REPL mode and this can't 4 | be disabled. However, it is run within neovim so you can expect the 5 | usual vim API functions to be available. 6 | 7 | Essentially, you can expect REPL behavior when running line-by-line 8 | of bloc-by-bloc lua script: 9 | 10 | ```lua 11 | a = 4 12 | b = 6 13 | print(a+5) -- <- 9 14 | 15 | a = 0 16 | 17 | print(a + b) -- <- 6 18 | ``` 19 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Lua_original.md: -------------------------------------------------------------------------------- 1 | ## Lua original 2 | 3 | Limitation/feature 4 | 5 | IF 6 | 7 | - your code selection contains "nvim' or "vim", even in comments, 8 | - you haven't explicitely selected Lua_original 9 | - your code snippet fails 10 | 11 | THEN 12 | 13 | Sniprun will use the lua-nvim interpreter instead of the normal 'lua' one. 14 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Mathematica_original.md: -------------------------------------------------------------------------------- 1 | ## Mathematica original 2 | 3 | ### QuickStart 4 | 5 | Check that `WolframKernel` and `pkill` are installed and on your path. 6 | 7 | Then, the setup 99% of people want: 8 | 9 | ```lua 10 | lua require'sniprun'.setup({ 11 | repl_enable = {'Mathematica_original'}, 12 | interpreter_options = { 13 | Mathematica_original = { 14 | use_javagraphics_if_contains = {'Plot'}, -- a pattern that need < how many seconds to keep the plot window open 33 | }, 34 | }, 35 | }) 36 | ``` 37 | 38 | This can be useful if you didn't already use the construct: 39 | 40 | `Plot[Sin[x], {x, 0, 10}, DisplayFunction -> CreateDialog]` 41 | 42 | 43 | If your selection contains a Plot (or matching pattern), in non-REPL mode 44 | Mathematica_original will never return (in order to keep the graph window open), 45 | this means other statements will not return output. 46 | 47 | As a general rule of thumb, in non-REPL mode, 48 | either sniprun a Plot _or_ normal statements 49 | 50 | ### Print on each sniprun (non-REPL only!) 51 | 52 | To make the experience more notebook/REPL -like, those options 53 | (incompatible with the true REPL mode) can be configured. 54 | 55 | They will wrap any/the last line, if they dont contain alread a 56 | Print, Plot or end with ";" or and open bracket 57 | 58 | !! WARNING !! This can lead to dangerous side-effects, mathematica contains 59 | very little documentation about this. To feel safe, you wouldn't use these 60 | unless you only execute code line-by-line. It may or may not work with blocs. 61 | 62 | ```lua 63 | lua require'sniprun'.setup({ 64 | interpreter_options = { 65 | Mathematica_original = { 66 | wrap_all_lines_with_print = false, -- wrap all lines making sense to print with Print[.]; 67 | wrap_last_line_with_print = false, -- wrap last line with Print[.] 68 | }, 69 | }, 70 | }) 71 | ``` 72 | 73 | ### Quirks of REPL-mode 74 | 75 | 76 | Enabling REPL for mathematica will launch a WolframKernel instance in the 77 | background. This session will close when the last neovim instance quits, 78 | but is only usable by one neovim instance. 79 | I hope you only need to sniprun mathematica snippets in one neovim instance, 80 | because anything else will probably crash. 81 | 82 | In REPL-mode, your first SnipRun command of the neovim instance is used to start 83 | the REPL kernel, and the selection is discarded. You'll have to re-run it, 84 | sorry, but that's the only way for now. 85 | 86 | In REPL-mode, just like in the normal interpreter, suffix your expressions with ';' 87 | if you don't want them to output something. 88 | I strongly recommend to suffix Plots with ';' 89 | 90 | ### Troubleshooting 91 | 92 | - No valid password found 93 | 94 | - For some reason, WolframKernel doesn't like being launched in the 95 | background if there is already a WolframKernel instance running. 96 | You can close your own, or `killall -9 WolframKernel` to kill all the 97 | WolframKernel still running, and re-open neovim. 98 | This will _also_ close the kernels you've launched yourself! 99 | 100 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Neorg_original.md: -------------------------------------------------------------------------------- 1 | ## Neorg original 2 | 3 | the Neorg\_original interpreter helps you running code 4 | blocs defined in neorg code blocs delimiters 5 | 6 | inline, switches and headers are not supported/ignored 7 | 8 | ### example 1 9 | ```neorg 10 | #name demo 11 | @code bash 12 | 13 | echo "lol" # << you can run sniprun on this line 14 | 15 | 16 | 17 | \# or the whole visual selection following: 18 | 19 | for i in {1..4};do 20 | 21 | echo $i 22 | 23 | done 24 | @end 25 | 26 | ``` 27 | 28 | ### example 2 29 | 30 | ```neorg 31 | #name demo_run_whole_bloc << running on this line or the line below will run the entire bloc 32 | @code rust 33 | 34 | println!("test"); 35 | println!("test2"); 36 | @end 37 | ``` 38 | 39 | 40 | Even though it is possible to have empty lines in between the #name tag and 41 | the @code block for this plugin this doesn't work. The #name has to be 42 | in the line directly above the @code block 43 | 44 | ```neorg 45 | #name name_tag_not_working << this #name tag doesn't run the code below 46 | 47 | 48 | @code rust 49 | 50 | println!("test"); 51 | println!("test2"); 52 | @end 53 | 54 | ``` 55 | 56 | **the language name must be there (otherwise the default * will be used) at 57 | the bloc start** and has to match the language name or the filetype associated 58 | 59 | \* python, but you can ofc configure that: 60 | 61 | ```lua 62 | require'sniprun'.setup({ 63 | interpreter_options = { 64 | Neorg_original = { 65 | default_filetype = 'bash' -- default filetype/language name 66 | } 67 | } 68 | }) 69 | ``` 70 | 71 | ### example 3: running named code blocs 72 | 73 | ```neorg 74 | #name mycodebloc 75 | @code rust 76 | println!("test"); 77 | @end 78 | ``` 79 | 80 | running `:%SnipRun mycodebloc` will run this code bloc 81 | (and any code bloc named similarly, case-insensitively) 82 | 83 | running `:%SnipRun` without any further arguments will run all the code blocs 84 | -------------------------------------------------------------------------------- /doc/sources/interpreters/OCaml_fifo.md: -------------------------------------------------------------------------------- 1 | ## OCaml fifo 2 | 3 | This interpreter relies on `ocaml` by default, but has been 4 | confirmed to work with `utop` in normal (non-REPL) mode. 5 | 6 | The default interpreter can be changed relatively safely for non-REPL mode: 7 | 8 | ```lua 9 | require'sniprun'.setup({ 10 | interpreter_options = { 11 | OCaml_fifo = { 12 | interpreter = "utop" 13 | } 14 | } 15 | } 16 | }) 17 | ``` 18 | 19 | ### REPL-like behavior 20 | 21 | ```lua 22 | require'sniprun'.setup({ 23 | repl_enable = { "OCaml_fifo" } 24 | }) 25 | ``` 26 | -------------------------------------------------------------------------------- /doc/sources/interpreters/OrgMode_original.md: -------------------------------------------------------------------------------- 1 | ## Orgmode original 2 | 3 | the Orgmode\_original interpreter helps you running code blocs 4 | defined in org code blocs delimiters 5 | 6 | inline, switches and headers are not supported/ignored 7 | 8 | ### example 1 9 | 10 | ```orgmode 11 | #+NAME: demo 12 | #+BEGIN_SRC bash 13 | 14 | echo "lol" # << you can run sniprun on this line 15 | 16 | 17 | 18 | \# or the whole visual selection following: 19 | 20 | for i in {1..4};do 21 | 22 | echo $i 23 | 24 | done 25 | #+END_SRC 26 | 27 | ``` 28 | 29 | 30 | ### example 2 31 | 32 | 33 | ```orgmode 34 | #+NAME: demo_run_whole_bloc 35 | #+BEGIN_SRC rust << running on this line will run the entire bloc 36 | 37 | println!("test"); 38 | println!("test2"); 39 | #+END_SRC 40 | ``` 41 | 42 | 43 | **the language name must be there (otherwise the default * will be used) at the bloc start** and has to match the language name or the filetype associated 44 | 45 | \* python, but you can ofc configure that: 46 | 47 | ```orgmode 48 | require'sniprun'.setup({ 49 | interpreter_options = { 50 | OrgMode_original = { 51 | default_filetype = 'bash' -- default filetype/language name 52 | } 53 | } 54 | }) 55 | ``` 56 | 57 | ### example 3: running named code blocs 58 | 59 | ``` 60 | #+NAME: mycodebloc 61 | #+BEGIN_SRC rust 62 | println!("test"); 63 | #+END_SRC 64 | ``` 65 | 66 | running `:%SnipRun mycodebloc` will run this code bloc 67 | (and any code bloc named similarly, case-insensitively) 68 | 69 | running `:%SnipRun` without any further arguments will run all the code blocs 70 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Plantuml_original.md: -------------------------------------------------------------------------------- 1 | ## PlantUML original 2 | 3 | This interpreter relies on `plantuml`, which needs to be available on the $PATH 4 | 5 | This interpreter supports the following options: 6 | 7 | ```lua 8 | require'sniprun'.setup({ 9 | interpreter_options = { 10 | Plantuml_original = { 11 | output_mode = "-tutxt", --# one of the options allowed by plantuml 12 | compiler = "/home/user/my_custom_plantuml_install/plantuml" 13 | } 14 | } 15 | } 16 | }) 17 | ``` 18 | 19 | You can add options to the 'compiler' key, but do not 20 | set "-pipe" (or it'll break output), and "-failfast2", 21 | "-nbthread auto" are already set. 22 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Prolog_original.md: -------------------------------------------------------------------------------- 1 | ## Prolog original 2 | 3 | This interpreter is currently a work in progress and is probably not usable 4 | 5 | The Prolog interpreter supports setting a different 6 | compiler/interpreter for prolog such as swi ('swipl') 7 | 8 | you can set it with the following key: 9 | 10 | ```lua 11 | require'sniprun'.setup({ 12 | interpreter_options = { 13 | Prolog_original = { interpreter = "swipl" } 14 | } 15 | } 16 | }) 17 | ``` 18 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Python3_fifo.md: -------------------------------------------------------------------------------- 1 | ## Python3 fifo 2 | 3 | This is a pipe-based implementation that has some quirks: 4 | 5 | You have to run sniprun once before being able to send code snippets 6 | to it (configure an autocmd?) 7 | 8 | A python REPL is launched in the background and won't quit till you exit neovim. 9 | 10 | This interpreter only works in REPL-mode, and is not the default for Python 11 | files, so to use it you should configure it as following: 12 | 13 | ```lua 14 | require'sniprun'.setup({ 15 | selected_interpreters = { 'Python3_fifo' }, 16 | repl_enable = {'Python3_fifo'}, 17 | }) 18 | ``` 19 | 20 | if a snippet produce an error important enough to crash the interpreter, 21 | you may be required to re-launch the kernel (with a `SnipRun`) 22 | 23 | setting a custom python interpreter and venv is also supported 24 | 25 | ```lua 26 | require'sniprun'.setup({ 27 | interpreter_options = { 28 | Python3_fifo = { 29 | interpreter = "python3.9", 30 | venv = {"venv_project1", "venv_project2", "../venv_project2"}, 31 | } 32 | } 33 | } 34 | }) 35 | ``` 36 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Python3_jupyter.md: -------------------------------------------------------------------------------- 1 | ## Python3 jupyter 2 | 3 | Unless you really really need this (for Ipython), I would strongly 4 | advise for the Python3_fifo interpreter which has way less potential issues 5 | 6 | ### Dependencies 7 | 8 | - jupyter 9 | 10 | (more specifically, you must be able to run `jupyter-kernel` and 11 | `jupyter-console` from the command line) 12 | 13 | ### Notes 14 | 15 | (As there is a different interpreter for Python, you may want to 16 | force the selection of Python3_jupyter with:) 17 | 18 | ```lua 19 | require'sniprun'.setup({ 20 | selected_interpreters={'Python3_jupyter'} 21 | }) 22 | ``` 23 | 24 | ### Limitations 25 | 26 | The code runs on a separate jupyter python3 kernel which will NOT interefere 27 | with your own running kernels. 28 | 29 | However, mind that the usual limitations of such kernels still apply: 30 | max duration of execution, etc... but you probably don't have to pay 31 | too much attention to this. 32 | 33 | The jupyter kernel also has a substantial overhead when it comes to running 34 | code, in addition to imports*, that means the Python3_jupyter interpreter may 35 | feel a bit slow compared to others, when the REPL functionnaluty relying on it. 36 | 37 | \* The Jupyter-based interpreter also doesn't support 38 | Python's "list-import" syntax such as: 39 | 40 | ```python 41 | from math import ( 42 | sin, 43 | cos 44 | ) 45 | ``` 46 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Python3_original.md: -------------------------------------------------------------------------------- 1 | ## Python3 original 2 | 3 | To get the REPL behaviour (inactive by default) working, you need to install 4 | the klepto python package: `pip install --user klepto` 5 | 6 | Then, to enable the REPL behavior for python in your config file 7 | 8 | ```lua 9 | require'sniprun'.setup({ 10 | repl_enable = {'Python3_original'} 11 | }) 12 | ``` 13 | 14 | HOWEVER, if you're interested in a very stable and solid REPL python 15 | interpreter, to process bigger amount of data or import some exotic 16 | modules (not supported by klepto), get a look at the Python3_fifo interpreter. 17 | 18 | With the REPL enabled, sniprunning a \* (star) import `from module import *` may 19 | not work, indeed the imports needs to be correctly saved/loaded by klepto. 20 | klepto manages variables, functions and modules but very special things may fail. 21 | 22 | Without REPL enabled, your python snip's will be executed faster 23 | (and not increasingly slower) and the correctness/cleanliness of the inner 24 | working is garanteed. By setting this, you can be sure your snip's will run 25 | free of side-effects and anything you would not want. 26 | 27 | With or without REPL, the star imports may also not be automatically fetched, 28 | even though normal imports will be. Python3_original has the 'Import' support 29 | level but that won"t work with star import, and I don't think we'll be able 30 | to make a workaround due to the philosophy 'run only what's necessary' of sniprun. 31 | 32 | To use a custom python interpreter ( maybe python2, or a particular version?) 33 | you can provide the following interpreter options:, using a venv is also 34 | supported (provide one or several relative paths "../venv" etc.. may be 35 | necessary if Neovim didn't set the current working directory at the root 36 | of the project (presumably next to the venv). 37 | 38 | ```lua 39 | require'sniprun'.setup({ 40 | interpreter_options = { 41 | Python3_original = { 42 | interpreter = "python3.9", 43 | venv = {"venv_project1", "venv_project2", "../venv_project2"}, 44 | } 45 | } 46 | } 47 | }) 48 | ``` 49 | -------------------------------------------------------------------------------- /doc/sources/interpreters/R_original.md: -------------------------------------------------------------------------------- 1 | ## R original 2 | 3 | Needs `Rscript` 4 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Ruby_original.md: -------------------------------------------------------------------------------- 1 | ## Ruby original 2 | 3 | This interpreter assumes ruby is installed and on your $PATH, but no extra configuration options are available 4 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Rust_original.md: -------------------------------------------------------------------------------- 1 | ## Rust original 2 | 3 | a custom compiler can be specified : 4 | 5 | ```lua 6 | require'sniprun'.setup({ 7 | interpreter_options = { 8 | Rust_original = { 9 | compiler = "rustc" 10 | } 11 | } 12 | } 13 | }) 14 | ``` 15 | -------------------------------------------------------------------------------- /doc/sources/interpreters/SQL_original.md: -------------------------------------------------------------------------------- 1 | ## SQL original 2 | 3 | This interpreter relies on `usql` being installed 4 | 5 | ```lua 6 | require'sniprun'.setup({ 7 | interpreter_options = { 8 | SQL_original = { 9 | interpreter = "/home/user/my_usql_install/usql --myoption" 10 | } 11 | } 12 | } 13 | }) 14 | ``` 15 | 16 | the option "-w" (do not prompt for the password) and --file are already passed 17 | by sniprun, so you should not pass conflicting/duplicate options. 18 | 19 | This interpreter will prompt you at first use what database you want to connect to, 20 | that is an address (including user & password if applicable) as you would 21 | pass to `usql` itself. In case the database is local (such as for sqlite), 22 | you should input an absolute path or a path relative to neovim's current 23 | working directory (`:pwd`). 24 | 25 | This address (and the possible user/password) is NOT stored anywhere, but sniprun 26 | will remember it as long as the neovim session stays open. You can use `:SnipReset` 27 | to clear sniprun's memory. 28 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Sage_fifo.md: -------------------------------------------------------------------------------- 1 | ## Sage fifo 2 | 3 | This is a pipe-based implementation: you have to run sniprun once before 4 | being able to send code snippets (configure an autcmd?) 5 | 6 | A sage REPL is launcher in the background and won't quit 7 | until you exit neovim (or after 10h). 8 | 9 | This interpreter only works in REPL-mode (and behaves REPL-like by default) 10 | 11 | two configurations keys are available: 12 | 13 | ```lua 14 | require'sniprun'.setup({ 15 | interpreter_options = { 16 | Sage_fifo = { 17 | interpreter = "sage", 18 | sage_user_config = 1, -- the actual value has no effect, only the presence of the key is important 19 | } 20 | } 21 | } 22 | }); 23 | ``` 24 | 25 | Sage\_fifo currently support auto-importing python imports 26 | -------------------------------------------------------------------------------- /doc/sources/interpreters/Scala_original.md: -------------------------------------------------------------------------------- 1 | ## Scala original 2 | 3 | Needs the `scalac' compiler on the $PATH 4 | -------------------------------------------------------------------------------- /doc/sources/interpreters/TypeScript_original.md: -------------------------------------------------------------------------------- 1 | ## TypeScript original 2 | 3 | Prior to NodeJS v22.6.0: 4 | Install `ts-node`, usually installed from npm 5 | 6 | `sudo npm install -g ts-node typescript` 7 | 8 | --- 9 | 10 | If you have NodeJS v22.6.0+: 11 | 12 | ```lua 13 | require'sniprun'.setup({ 14 | interpreter_options = { 15 | TypeScript_original = { 16 | interpreter = 'node' 17 | } 18 | } 19 | } 20 | }) 21 | ``` 22 | 23 | [^1]: `ts-node` and `typescript` packages are no longer needed for newer NodeJS versions 24 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Runnning Sniprun Installer" 4 | local_version="v$(grep ^version Cargo.toml | cut -d "\"" -f 2)" 5 | 6 | force_build=$1 7 | current_branch=$(git rev-parse --abbrev-ref HEAD) 8 | if [ "$current_branch" = "dev" ]; then 9 | force_build=1 10 | fi 11 | 12 | arch=$(uname) 13 | if [ "$arch" != "Linux" ] && [ "$force_build" != 1 ]; then 14 | echo "Looks you are not running Linux: Mac users have to compile sniprun themselves and thus need the Rust toolchain" 15 | force_build=1 16 | fi 17 | 18 | cargo_build() { 19 | if command -v cargo >/dev/null; then 20 | echo "Building sniprun from source..." 21 | cargo build --release 2>&1 22 | echo "Done (status: $?)" 23 | return 0 24 | else 25 | echo "Could not find cargo in \$PATH: the Rust toolchain is required to build Sniprun" 26 | return 1 27 | fi 28 | } 29 | 30 | get_latest_release() { 31 | curl --silent "https://api.github.com/repos/michaelb/sniprun/releases/latest" | LC_ALL=C tr -d "\n" | sed -e 's|^.*\("tag_name"[^,]*\).*|\1|' | cut -d'"' -f4 # sed keeps matchgroup "tag_name": "vX.Z.Z", cut -d '"' does the rest 32 | } 33 | 34 | # download the sniprun binary (of the specified version) from Releases 35 | download() { 36 | echo "Downloading sniprun binary: $1" 37 | curl -fsSL "https://github.com/michaelb/sniprun/releases/download/$1/sniprun" --output sniprun 38 | mkdir -p target/release/ 39 | mv -f sniprun target/release/ 40 | } 41 | 42 | # call download, make executable, and return status 43 | fetch_prebuilt_binary() { 44 | if (download "$1"); then 45 | chmod a+x target/release/sniprun 46 | echo "Done" 47 | return 0 48 | else 49 | return 1 50 | fi 51 | } 52 | 53 | 54 | remote_version=$(get_latest_release) 55 | 56 | if [ "$force_build" ]; then 57 | echo "Compiling sniprun locally:" 58 | neovim_version=$(nvim --version | head -n 1 | cut -d . -f 2) # 4 -> neovim 0.4.x 59 | if [ "$neovim_version" = "4" ]; then 60 | echo "Sniprun 0.4.9 is the highest version supported on neovim 0.4.x" 61 | git reset --hard v0.4.9 62 | fi 63 | cargo_build 64 | else 65 | 66 | tag_to_fetch=$remote_version 67 | neovim_version=$(nvim --version | head -n 1 | cut -d . -f 2) # 4 -> neovim 0.4.x 68 | if [ "$neovim_version" = "4" ]; then 69 | echo "Sniprun 0.4.9 is the highest version supported on neovim 0.4.x" 70 | git reset --hard v0.4.9 71 | tag_to_fetch="v0.4.9" 72 | fi 73 | 74 | fetch_prebuilt_binary "$tag_to_fetch" 75 | 76 | # if download failed 77 | if [ $? = 1 ]; then 78 | echo "Failed to download sniprun, check your network or build locally?" 79 | fi 80 | fi 81 | -------------------------------------------------------------------------------- /lua/sniprun/api.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | M.listeners = {} 3 | M.closers = {} 4 | 5 | local sniprun = require('sniprun') 6 | local sniprun_path = vim.fn.fnamemodify(vim.api.nvim_get_runtime_file("lua/sniprun.lua", false)[1], ":p:h") .. "/.." 7 | 8 | function M.run_range(range_start, range_end, filetype, config) 9 | local override = {} 10 | override.filetype = filetype 11 | local lconfig = config or sniprun.config_values 12 | lconfig["sniprun_root_dir"] = sniprun.config_values.sniprun_path 13 | sniprun.notify('run', range_start, range_end, lconfig, "", override) 14 | end 15 | 16 | function M.run_string(codestring, filetype, config) 17 | local override = {} 18 | override.codestring = codestring 19 | override.filetype = filetype or "" 20 | local lconfig = config or sniprun.config_values 21 | lconfig["sniprun_root_dir"] = sniprun.config_values.sniprun_path 22 | local _, col = unpack(vim.api.nvim_win_get_cursor(0)) 23 | sniprun.notify('run', col, col, lconfig, "", override) 24 | end 25 | 26 | function M.register_listener(f) 27 | if type(f) ~= 'function' then 28 | print("Only functions can be registered") 29 | end 30 | assert(type(f) == 'function') 31 | table.insert(M.listeners, f) 32 | end 33 | 34 | function M.register_closer(f) 35 | if type(f) ~= 'function' then 36 | print("Only functions can be registered") 37 | end 38 | assert(type(f) == 'function') 39 | table.insert(M.closers, f) 40 | end 41 | 42 | return M 43 | -------------------------------------------------------------------------------- /lua/sniprun/input.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- function M.floating_win_ask(message) 4 | -- 5 | -- local w = 0 6 | -- local h = -1 7 | -- local bp = vim.api.nvim_win_get_cursor(0) -- message at current cursor position 8 | -- local bufnr = vim.api.nvim_create_buf(false, true) 9 | -- for line in message:gmatch("([^\n]*)\n?") do 10 | -- h = h + 1 11 | -- w = math.max(w, string.len(line)) 12 | -- vim.api.nvim_buf_set_lines(bufnr, h, h + 1, false, { line }) 13 | -- end 14 | -- if h ~= 0 then 15 | -- M.fw_handle = vim.api.nvim_open_win(bufnr, false, 16 | -- { 17 | -- relative = 'win', 18 | -- width = w + 1, 19 | -- height = h, 20 | -- bufpos = bp, 21 | -- focusable = false, 22 | -- style = 'minimal', 23 | -- border = "single" 24 | -- }) 25 | -- vim.api.nvim_win_call(M.fw_handle, function() 26 | -- vim.api.nvim_exec_autocmds("BufWinEnter", { buffer = bufnr, modeline = false }) 27 | -- end) 28 | -- vim.api.nvim_set_current_win(M.fw_handle) 29 | -- end 30 | -- end 31 | 32 | function M.vim_input(message) 33 | print(vim.fn.input(message)) 34 | end 35 | 36 | return M 37 | -------------------------------------------------------------------------------- /lua/sniprun/live_mode.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function deepcopy(orig) 4 | local orig_type = type(orig) 5 | local copy 6 | if orig_type == 'table' then 7 | copy = {} 8 | for orig_key, orig_value in next, orig, nil do 9 | copy[deepcopy(orig_key)] = deepcopy(orig_value) 10 | end 11 | setmetatable(copy, deepcopy(getmetatable(orig))) 12 | else -- number, string, boolean, etc 13 | copy = orig 14 | end 15 | return copy 16 | end 17 | 18 | function M.run() 19 | local sa = require('sniprun.api') 20 | local line = vim.api.nvim_win_get_cursor(0)[1] 21 | local ft = vim.bo.filetype 22 | local opts = deepcopy(require('sniprun').config_values) 23 | opts.display = opts.live_display 24 | opts.show_no_output = {} 25 | sa.run_range(line, line, ft, opts) 26 | end 27 | 28 | function M.enable() 29 | vim.cmd [[ 30 | augroup _sniprunlive 31 | autocmd! 32 | autocmd TextChanged * lua require'sniprun.live_mode'.run() 33 | autocmd TextChangedI * lua require'sniprun.live_mode'.run() 34 | augroup end 35 | lua require'sniprun.live_mode'.run() 36 | ]] 37 | vim.notify "Enabled Sniprun live mode" 38 | end 39 | 40 | function M.disable() 41 | M.remove_augroup "_sniprunlive" 42 | require('sniprun.display').clear_virtual_text() 43 | vim.notify "Disabled Sniprun live mode" 44 | end 45 | 46 | function M.toggle() 47 | if vim.fn.exists "#_sniprunlive#TextChanged" == 0 then 48 | M.enable() 49 | else 50 | M.disable() 51 | end 52 | end 53 | 54 | function M.remove_augroup(name) 55 | if vim.fn.exists("#" .. name) == 1 then 56 | vim.cmd("au! " .. name) 57 | end 58 | end 59 | 60 | vim.cmd [[ command! SnipLive execute 'lua require("sniprun.live_mode").toggle()' ]] 61 | vim.api.nvim_set_keymap("n", "SnipLive", ":lua require'sniprun.live_mode'.toggle()", { silent = true }) 62 | 63 | return M 64 | -------------------------------------------------------------------------------- /plugin/sniprun.vim: -------------------------------------------------------------------------------- 1 | lua require'sniprun'.initial_setup() 2 | " will NOT erase an user configuration setup before or after this call, 3 | " and will define commands necessary to backward compatibility 4 | -------------------------------------------------------------------------------- /ressources/CONTRIBUTING_REPL.md: -------------------------------------------------------------------------------- 1 | # Making a REPL-capable interpreter for sniprun 2 | 3 | ## Is it possible ? 4 | 5 | Yes, most of the time, if the language already has an available interpreter. It _could_ be possible otherwise but has yet to be really done. 6 | 7 | To avoid confusion, we'll call the language interpreter 'interpreter', and sniprun's part (implementing the Interpreter trait) the runner. 8 | 9 | ## How ? 10 | Two ways, mostly. Either: 11 | - your language has 'quirks' (like for R and Python with the klepto module, see R\_original and Python3\_original) that allow current variables and stuff to be 'saved' to a file then loaded 12 | 13 | or 14 | 15 | - you make use of a named pipe (fifo) and pipe what sniprun says into it. the pipe is connected to a live, running, interpreter for your language. Its output is written to a file and sniprun waits for landmarks (start, end) to be printed. 16 | 17 | 18 | I strongly advise the latter methodology, which has several advantages that I won't discuss here, but can be harder to implement if your language's interpreter has weird stdin/stdout/stderr behavior. Like non-disablable prompts printed to stdout. 19 | 20 | 21 | ## How to implement a pipe-based repl-capable runner 22 | 23 | The best example I'm going to discuss is Python3\_fifo, even if it's a bit bloated from python-specific things. 24 | 25 | Just like you implemented the Interpreter trait for a conventional runner, you'll have to implement the ReplLikeInterpreter trait. Another trait (InterpreterUtils) is automatically implemented and provides features & data persistency to help you survive across different/independent runs. 26 | 27 | 1. Running something in the background: 28 | 29 | ```rust 30 | fn fetch_code_repl(&mut self) -> Result<(), SniprunError> { 31 | if !self.read_previous_code().is_empty() { 32 | // nothing to do, kernel already running 33 | 34 | .... 35 | 36 | self.fetch_code()?; 37 | Ok(()) 38 | } else { 39 | 40 | let init_repl_cmd = self.data.sniprun_root_dir.clone() + "/ressources/init_repl.sh"; 41 | 42 | match daemon() { 43 | Ok(Fork::Child) => { // background child, launch interpreter 44 | let _res = Command::new("....."); // bash init_repl_cmd args 45 | 46 | let pause = std::time::Duration::from_millis(36_000_000); 47 | std::thread::sleep(pause); 48 | 49 | return Err(SniprunError::CustomError("Timeout expired for python3 REPL".to_owned())); 50 | } 51 | Ok(Fork::Parent(_)) => {} // do nothing 52 | Err(_) => info!( 53 | "Python3_fifo could not fork itself to the background to launch the kernel" 54 | ), 55 | }; 56 | 57 | let pause = std::time::Duration::from_millis(100); 58 | std::thread::sleep(pause); 59 | 60 | self.save_code("kernel_launched\n".to_owned()); 61 | let v = vec![(self.data.range[0] as usize, self.data.range[1] as usize)]; 62 | Err(SniprunError::ReRunRanges(v)) // special error that tells sniprun to re-run a snippet 63 | // which is simpler than to embed calls to fetch/build/.. here 64 | } 65 | ``` 66 | The important thing to note is that `self.read_previous_code()` is used to determine whether a kernel was already launched; (`self.get_pid()/set_pid()` can be used to store an incrementing number of 'runs' or the child's PID, or whatever. 67 | 68 | 2. Landmarks 69 | 70 | ```rust 71 | fn add_boilerplate_repl(&mut self) -> Result<(), SniprunError> { 72 | self.add_boilerplate()?; 73 | let start_mark = String::from("\nprint(\"sniprun_started_id=") 74 | + &self.current_output_id.to_string() 75 | + "\")\n"; 76 | let end_mark = String::from("\nprint(\"sniprun_finished_id=") 77 | + &self.current_output_id.to_string() 78 | + "\")\n"; 79 | let start_mark_err = String::from("\nprint(\"sniprun_started_id=") 80 | + &self.current_output_id.to_string() 81 | + "\", file=sys.stderr)\n"; 82 | let end_mark_err = String::from("\nprint(\"sniprun_finished_id=") 83 | + &self.current_output_id.to_string() 84 | + "\", file=sys.stderr)\n"; 85 | .... 86 | ``` 87 | 88 | the user's code has to be wrapped with 4 landmarks that prints 'start run°X', 'end run n°X' messages. Snipruns uses them to determine when the user's code has finished executing. It's then displayed. Note that things can't be displayed 'live', and if someone launches an infinite loop, they won't have any output. 89 | 90 | 91 | 3. Waiting for output 92 | ``` rust 93 | fn wait_out_file (....){ 94 | loop { 95 | std::thread::sleep( 50 ms); 96 | 97 | //check for content matching the current ID in file for stderr 98 | 99 | //check for content matching the current ID in file for stdout 100 | 101 | //break when something found & finished 102 | } 103 | } 104 | ``` 105 | is executed & returned at the end of `execute_repl` that firsts send the user's snippet (wrapped with landmarks) to the FIFO pipe. 106 | 107 | 4. Helper scripts 108 | Though not very documented, the `ressources/init_repl.sh` and `ressources/launcher.sh` script are resuable for other runners than Python3\_fifo (see Mathematica that has its own similar scripts in `src/interpreters/Mathematica_original/`. They take care of plugging together the fifo, stdout, stderr files and the interpreter's process. They also take care of closing the interpreter (and free the ressources) when nvim exits 109 | 110 | 111 | ### End notes: 112 | - you should take care of separating the working directories for repl-capable interpreters from different neovim sessions, to avoid having nonsense because of mixed fifo and output files content: 113 | 114 | ``` 115 | fn new_with_level(...) 116 | Box::new(Python3_fifo { 117 | cache_dir: data.work_dir.clone() + "/python3_fifo/" + &Python3_fifo::get_nvim_pid(&data), 118 | .... 119 | ``` 120 | 121 | - disable prompts for your interpreter. They'll pollute stdout. For example, in python, you'll have to set `sys.ps1` and `sys.ps2` to `""`. 122 | 123 | -------------------------------------------------------------------------------- /ressources/api.md: -------------------------------------------------------------------------------- 1 | The results are displayed via the [nvim-notify](https://github.com/rcarriga/nvim-notify) plugin 2 | 3 | 4 | The color of the notification and the title reflect the status (ok, error) 5 | 6 | ``` 7 | lua << EOF 8 | require'sniprun'.setup({ 9 | display = { "NvimNotify" }, 10 | }) 11 | ``` 12 | ![](visual_assets/api.png) 13 | 14 | 15 | Changing the contents of the buffer will generally not interfere with sniprun with the exception of running multiple code blocs in a markup language (such as markdown or orgmode), because sniprun gets the list of the positions of the code blocs once, before running & displaying once per code bloc 16 | -------------------------------------------------------------------------------- /ressources/asciiart.txt: -------------------------------------------------------------------------------- 1 | _____ _ _____ 2 | / ____| (_) | __ \ 3 | | (___ _ __ _ _ __ | |__) | _ _ __ 4 | \___ \| '_ \| | '_ \| _ / | | | '_ \ 5 | ____) | | | | | |_) | | \ \ |_| | | | | 6 | |_____/|_| |_|_| .__/|_| \_\__,_|_| |_| 7 | | | 8 | |_| 9 | -------------------------------------------------------------------------------- /ressources/display_classic.md: -------------------------------------------------------------------------------- 1 | The result is displayed at the bottom of the window, in the command-line area. 2 | 3 | Errors are displayed in red, and multiline output is supported 4 | 5 | 6 | 7 | Activated by default, to change activate, add or remove "Classic" to the 'display' key in sniprun configuration: 8 | 9 | ``` 10 | lua << EOF 11 | require'sniprun'.setup({ 12 | display = { "Classic" }, 13 | }) 14 | EOF 15 | ``` 16 | 17 | 18 | ![](visual_assets/classic.png) 19 | -------------------------------------------------------------------------------- /ressources/display_floating_window.md: -------------------------------------------------------------------------------- 1 | Display the resutls in a temporary floating window. 2 | 3 | The floating window is closed on the CursorMoved event, or from `:SnipClose` 4 | 5 | The highlight groups used are : 6 | - "SniprunFloatingWinOk" 7 | - "SniprunFloatingWinErr" 8 | 9 | 10 | You can configure the displau key as shown to enable temporary floating windows display mode 11 | 12 | ``` 13 | require'sniprun'.setup({ 14 | display = { "TempFloatingWindow" }, 15 | }) 16 | ``` 17 | 18 | OR (enable only for long outputs) 19 | 20 | ``` 21 | require'sniprun'.setup({ 22 | display = { "LongTempFloatingWindow" }, 23 | }) 24 | ``` 25 | ![](visual_assets/floating_window.png) 26 | 27 | -------------------------------------------------------------------------------- /ressources/display_notify.md: -------------------------------------------------------------------------------- 1 | # nvim-notify as display option 2 | 3 | This plugin : https://github.com/rcarriga/nvim-notify 4 | 5 | must be installed in order to use this configuration option 6 | Sniprun will use the global configuration of the plugin 7 | 8 | To use it, configure sniprun with: 9 | 10 | ``` 11 | lua << EOF 12 | require'sniprun'.setup({ 13 | display = {"NvimNotify"}, 14 | }) 15 | EOF 16 | ``` 17 | 18 | The notification timeout can be changed with this configuration option: 19 | 20 | ``` 21 | display_options = { 22 | notification_timeout = 5 -- timeout for nvim_notify output 23 | }, 24 | ``` 25 | -------------------------------------------------------------------------------- /ressources/display_terminal.md: -------------------------------------------------------------------------------- 1 | A vertical split is opened to the right, and it display (non-interactively) sniprun output 2 | 3 | Can be closed with `:SnipClose` (or a shortcut to `SnipClose`) 4 | 5 | Highlighting is not supported yet 6 | 7 | if you experience wrapping of the header line '---- OK ---' due to the presence of a number column, you can (and should anyway) set 8 | 9 | ```vim 10 | autocmd TermOpen * setlocal nonu 11 | ``` 12 | 13 | in your configuration. 14 | 15 | 16 | 17 | To activate, add "Terminal" to the 'display' key in sniprun configuration: 18 | 19 | ``` 20 | lua << EOF 21 | require'sniprun'.setup({ 22 | display = { "Terminal" }, 23 | }) 24 | EOF 25 | ``` 26 | You can change the width of the terminal by using the display option in the configuration: 27 | ``` 28 | display_options = { 29 | terminal_scrollback = vim.o.scrollback, -- change terminal display scrollback lines 30 | terminal_line_number = false, -- whether show line number in terminal window 31 | terminal_signcolumn = false, -- whether show signcolumn in terminal window 32 | terminal_width = 45, -- change the terminal display option width 33 | }, 34 | ``` 35 | 36 | 37 | ![](visual_assets/terminal.png) 38 | 39 | 40 | If you also want to print the code being executed to the 'terminal', then use `"TerminalWithCode"` instead in the 'display' key. 41 | 42 | 43 | 44 | ![](visual_assets/terminalWithCode.png) 45 | 46 | -------------------------------------------------------------------------------- /ressources/display_virtualtext.md: -------------------------------------------------------------------------------- 1 | Virtual text is permanently displayed at the right of the line (/ last line of bloc), and can be cleared with 2 | `:SnipClose` (or a shortcut to `SnipClose`). 3 | 4 | Output for ok and error results are highlighted with the groups 5 | `SniprunVirtualTextOk` and `SniprunVirtualTextErr` 6 | 7 | One can choose to display only the 'ok' results or 'error' results or both in the display configuration via the keys: 8 | - "SniprunVirtualTextOk" 9 | - "SniprunVirtualTextErr" 10 | 11 | 12 | To switch on/off (VirtualTextOk is activated by default), you add/remove these value to the display key in sniprun configuration: 13 | 14 | ``` 15 | lua < for ok, \... for errors) 25 | 26 | ![](visual_assets/virtual_text.png) 27 | -------------------------------------------------------------------------------- /ressources/gitscript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | get_latest_release() { 5 | curl --silent "https://api.github.com/repos/michaelb/sniprun/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value 6 | } 7 | 8 | local_version=v$(cat Cargo.toml | grep -m 1 version | cut -d "\"" -f 2) 9 | remote_version=$(get_latest_release) 10 | 11 | branch_name="$(git symbolic-ref HEAD 2>/dev/null)" 12 | 13 | echo -n "Version : " $local_version 14 | if [ $local_version == $remote_version ]; then 15 | echo -n " (up-to-date)" 16 | elif [[ "$local_version" == *"beta"* ]];then 17 | echo -n " (latest stable is $remote_version)" 18 | else 19 | echo -n " (update to " $remote_version "is available)" 20 | fi 21 | 22 | 23 | if [[ "$branch_name" == *"dev" ]]; then 24 | commit=$(git rev-parse --short HEAD) 25 | echo -n " dev branch, git HEAD: $commit" 26 | else 27 | # on main branch, only show version 28 | echo "" 29 | fi 30 | -------------------------------------------------------------------------------- /ressources/go_install.sh: -------------------------------------------------------------------------------- 1 | export CURRENT_BUILD_PATH=$(pwd) 2 | echo $PATH 3 | rm -rf $HOME/golang 4 | rm -rf $HOME/gopath 5 | mkdir -p $HOME/golang 6 | mkdir -p $HOME/gopath 7 | curl http://storage.googleapis.com/golang/go1.5.2.linux-amd64.tar.gz 2>/dev/null > go1.5.2.linux-amd64.tar.gz 8 | tar -C $HOME/golang -xzf go1.5.2.linux-amd64.tar.gz 9 | export GOROOT=$HOME/golang/go 10 | export GOPATH=$HOME/gopath 11 | export PATH=$PATH:$GOROOT/bin 12 | export PATH=$PATH:$GOPATH/bin 13 | (if [[ "$(go version)" == *"go version go1.5"* ]]; then echo "✓ Go binary installed!"; else echo "Go binary not installed"; exit -1; fi); 14 | go version 15 | echo $PATH 16 | go env 17 | which go 18 | alias go=$HOME/golang/go/bin/go 19 | -------------------------------------------------------------------------------- /ressources/init_repl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # this script takes 3 (or more) args. 4 | # $1 is a path to a working directory 5 | # $2 is the PID of the parent neovim session. All processes created here will be killed when the parent exits 6 | # $3, $4 ... are the repl command to launch (ex: "deno", "repl" "-q") 7 | 8 | 9 | working_dir="$1/fifo_repl" 10 | log=$working_dir/../background_repl_process.log 11 | if test -e "$working_dir" ; then 12 | echo "process already present" >> $log 13 | exit 1 14 | fi 15 | 16 | 17 | 18 | 19 | pipe=pipe_in 20 | out=out_file 21 | err=err_file 22 | 23 | nvim_pid=$2 24 | 25 | shift 2 26 | repl="$@" # the rest 27 | 28 | 29 | rm -rf $working_dir/ 30 | mkdir -p $working_dir 31 | echo "pid of parent neovim session is $nvim_pid" >> $log 32 | echo "setting up things" >> $log 33 | 34 | mkfifo $working_dir/$pipe 35 | touch $working_dir/$out 36 | sleep 36000 > $working_dir/$pipe & 37 | 38 | echo "\cat " $working_dir/$pipe " | " $repl > $working_dir/real_launcher.sh 39 | chmod +x $working_dir/real_launcher.sh 40 | 41 | echo $repl " process started at $(date +"%F %T")." >> $log 42 | bash $working_dir/real_launcher.sh > $working_dir/$out 2> $working_dir/$err & 43 | 44 | echo "done" >> $log 45 | 46 | 47 | # wait for the parent nvim process to exit, then kill the sniprun process that launched this script 48 | 49 | while ps -p $nvim_pid ;do 50 | sleep 1 51 | done 52 | 53 | 54 | pkill -P $$ 55 | 56 | echo $repl " and other background processes terminated at $(date +"%F %T")." >> $log 57 | 58 | rm -rf $working_dir 59 | -------------------------------------------------------------------------------- /ressources/install_all_compilers_ci.sh: -------------------------------------------------------------------------------- 1 | sudo add-apt-repository ppa:neovim-ppa/stable -y 2 | sudo apt-get update 3 | sudo apt-get install neovim # install neovim 0.5+ 4 | 5 | if ! command -v ocaml &> /dev/null 6 | then 7 | sudo apt-get install ocaml 8 | fi 9 | 10 | if ! command -v ghc &> /dev/null 11 | then 12 | sudo apt-get install haskell-platform -y 13 | fi 14 | 15 | if ! command -v node &> /dev/null 16 | then 17 | sudo apt-get install -y nodejs 18 | fi 19 | 20 | if ! command -v npm &> /dev/null 21 | then 22 | sudo apt-get install -y npm 23 | fi 24 | 25 | if ! command -v coffee &> /dev/null 26 | then 27 | npm install -g coffee-script 28 | fi 29 | 30 | if ! command -v ts-node &> /dev/null 31 | then 32 | npm install -g typescript 33 | npm install -g ts-node 34 | fi 35 | 36 | if ! command -v Rscript &> /dev/null 37 | then 38 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9 39 | sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu focal-cran40/' 40 | sudo apt-get install r-base 41 | fi 42 | 43 | #ADA 44 | if ! command -v gnatmake &> /dev/null 45 | then 46 | sudo apt-get install gnat 47 | fi 48 | 49 | if ! command -v scalac &> /dev/null 50 | then 51 | sudo apt-get install scala 52 | fi 53 | pip3 install jupyter 54 | 55 | if ! command -v lua &> /dev/null 56 | then 57 | sudo apt-get install lua5.3 58 | fi 59 | 60 | if ! command -v sage &> /dev/null 61 | then 62 | sudo apt-get install sagemath 63 | fi 64 | 65 | # sudo apt-get install gprolog 66 | 67 | if ! command -v dotnet &> /dev/null 68 | then 69 | sudo apt-get install dotnet 70 | fi 71 | 72 | if ! command -v clojure &> /dev/null 73 | then 74 | sudo apt-get install clojure 75 | fi 76 | 77 | if ! command -v plantuml &> /dev/null 78 | then 79 | sudo apt-get install plantuml 80 | fi 81 | 82 | 83 | if ! command -v go &> /dev/null 84 | then 85 | ./ressources/go_install.sh 86 | export PATH=$PATH:$HOME/golang/go/bin/ 87 | fi 88 | 89 | if ! command -v mono &> /dev/null 90 | then 91 | sudo apt-get install dirmngr gnupg apt-transport-https ca-certificates -y 92 | sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF 93 | sudo apt-add-repository 'deb https://download.mono-project.com/repo/ubuntu stable-bionic main' 94 | sudo apt-get update 95 | sudo apt-get install mono-complete -y 96 | fi 97 | 98 | # deno for typescript and javascript 99 | # cargo install deno --locked # too long, takes 20 min! 100 | curl -fsSL https://deno.land/x/install/install.sh | sh 101 | cp $HOME/.deno/bin/* $HOME/.cargo/bin 102 | -------------------------------------------------------------------------------- /ressources/launcher_repl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | \cat $1 > $2 3 | -------------------------------------------------------------------------------- /ressources/sync_repl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | working_dir="$1/fifo_repl" 3 | echo "sync requested" >> $working_dir/log 4 | 5 | pipe=pipe_in 6 | out=out_file 7 | err=err_file 8 | 9 | echo "" > $working_dir/$pipe 10 | 11 | echo "sync done" >> $working_dir/log 12 | -------------------------------------------------------------------------------- /ressources/visual_assets/760091.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/760091.png -------------------------------------------------------------------------------- /ressources/visual_assets/Sniprun_social_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/Sniprun_social_preview.png -------------------------------------------------------------------------------- /ressources/visual_assets/Sniprun_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/Sniprun_transparent.png -------------------------------------------------------------------------------- /ressources/visual_assets/api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/api.png -------------------------------------------------------------------------------- /ressources/visual_assets/classic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/classic.png -------------------------------------------------------------------------------- /ressources/visual_assets/demo_c.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/demo_c.gif -------------------------------------------------------------------------------- /ressources/visual_assets/demo_repl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/demo_repl.png -------------------------------------------------------------------------------- /ressources/visual_assets/demo_rust.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/demo_rust.gif -------------------------------------------------------------------------------- /ressources/visual_assets/display_virtline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/display_virtline.png -------------------------------------------------------------------------------- /ressources/visual_assets/error_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/error_example.png -------------------------------------------------------------------------------- /ressources/visual_assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/favicon.ico -------------------------------------------------------------------------------- /ressources/visual_assets/floating_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/floating_window.png -------------------------------------------------------------------------------- /ressources/visual_assets/logo-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/logo-only.png -------------------------------------------------------------------------------- /ressources/visual_assets/logo-only.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/logo-only.xcf -------------------------------------------------------------------------------- /ressources/visual_assets/logo-transparent.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/logo-transparent.xcf -------------------------------------------------------------------------------- /ressources/visual_assets/logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/logo.xcf -------------------------------------------------------------------------------- /ressources/visual_assets/nvimnotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/nvimnotify.png -------------------------------------------------------------------------------- /ressources/visual_assets/rust_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/rust_error.png -------------------------------------------------------------------------------- /ressources/visual_assets/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/terminal.png -------------------------------------------------------------------------------- /ressources/visual_assets/terminalWithCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/terminalWithCode.png -------------------------------------------------------------------------------- /ressources/visual_assets/virtual_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/sniprun/a90c65846c3c18efc3f799d8b6dc26af9d4a69ed/ressources/visual_assets/virtual_text.png -------------------------------------------------------------------------------- /src/daemonizer.rs: -------------------------------------------------------------------------------- 1 | //! Derived from https://github.com/immortal/fork 2 | 3 | /// Fork result 4 | pub enum Fork { 5 | Parent(libc::pid_t), 6 | Child, 7 | } 8 | 9 | /// Close file descriptors stdin,stdout,stderr 10 | /// 11 | /// # Errors 12 | /// returns `-1` if error 13 | pub fn close_fd() -> Result<(), i32> { 14 | match unsafe { libc::close(0) } { 15 | -1 => Err(-1), 16 | _ => match unsafe { libc::close(1) } { 17 | -1 => Err(-1), 18 | _ => match unsafe { libc::close(2) } { 19 | -1 => Err(-1), 20 | _ => Ok(()), 21 | }, 22 | }, 23 | } 24 | } 25 | 26 | pub fn fork() -> Result { 27 | let res = unsafe { libc::fork() }; 28 | match res { 29 | -1 => Err(-1), 30 | 0 => Ok(Fork::Child), 31 | res => Ok(Fork::Parent(res)), 32 | } 33 | } 34 | 35 | pub fn setsid() -> Result { 36 | let res = unsafe { libc::setsid() }; 37 | match res { 38 | -1 => Err(-1), 39 | res => Ok(res), 40 | } 41 | } 42 | pub fn daemon() -> Result { 43 | match fork() { 44 | Ok(Fork::Parent(child_pid)) => Ok(Fork::Parent(child_pid)), 45 | Ok(Fork::Child) => setsid().and_then(|_| { 46 | close_fd()?; 47 | // close additionnal fds (logs, socket ?) 48 | let keep_fds = []; 49 | 50 | unsafe { 51 | close_fds::close_open_fds(3, &keep_fds); 52 | }; 53 | 54 | fork() 55 | }), 56 | Err(n) => Err(n), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug, Error, Eq, PartialEq)] 4 | #[allow(dead_code)] 5 | pub enum SniprunError { 6 | ///this error should only be raised when something goes very wrong, 7 | ///and you can't figure out what 8 | #[error("Unknown error: {0}")] 9 | UnknownError(String), 10 | 11 | ///internal error, should only be raised by Sniprun-specific code; 12 | ///*not* in languages interpreters 13 | #[error("Internal error: {0}")] 14 | InternalError(String), 15 | 16 | /// raised if code cannot be fetched from files for whatever reason 17 | #[error("Cannot fetch code from files")] 18 | FetchCodeError, 19 | 20 | ///when the user's code run into problems because of an interpreter's implementation 21 | /// It's also the only Err that won't clear the virtual text if VirtualTextOk is selected 22 | #[error("Interpreter limitation error: {0}")] 23 | InterpreterLimitationError(String), 24 | 25 | /// raised when code couldn't be run because of either incorrect code or 26 | /// UnsufficientSupportLevel but the language interpreter cannot determine which one 27 | #[error("Code contains errors")] 28 | InterpreterError, 29 | 30 | /// should be raised when users code fail to run but code is asserted correct 31 | #[error("Support level not high enough for this language and code snippet")] 32 | UnsufficientSupportLevel, 33 | /// errors raised if the user code is incorrect and fail a compile-time 34 | /// (and not because the language interpreter failed to the needed code/imports 35 | #[error("Compile-time error: {0}")] 36 | CompilationError(String), 37 | 38 | /// errors raised if the user code is incorrect and fail a run-time (and not because the language interpreter failed to fetch the needed code/imports 39 | #[error("RuntimeError: {0}")] 40 | RuntimeError(String), 41 | 42 | ///custom error for advanced interpreters, the error will be displayed as-is 43 | #[error("{0}")] 44 | CustomError(String), 45 | 46 | /// Divide one sniprun into many. Useful for markup language, when several 47 | /// code blocs are to be run from 1 'sniprun' command 48 | #[error("")] 49 | ReRunRanges(Vec<(usize, usize)>), 50 | } 51 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use crate::error::SniprunError; 2 | use neovim_lib::{Neovim, NeovimApi}; 3 | use std::sync::{Arc, Mutex}; 4 | use std::time::Duration; 5 | 6 | pub fn vim_input_ask(message: &str, nvim: &Arc>) -> Result { 7 | nvim.lock() 8 | .unwrap() 9 | .session 10 | .set_timeout(Duration::from_secs(20)); 11 | let res = nvim.lock().unwrap().command_output(&format!( 12 | "lua require\"sniprun.input\".vim_input(\"{}\")", 13 | message.replace('\n', "\\n"), 14 | )); 15 | match res { 16 | Err(_) => Err(SniprunError::CustomError( 17 | "Timeout waiting for user input".into(), 18 | )), 19 | Ok(input) => Ok(input), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/interpreters/Ada_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct Ada_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | 10 | ///specific to compiled languages, can be modified of course 11 | ada_work_dir: String, 12 | bin_path: String, 13 | main_file_path: String, 14 | // you can and should add fields as needed 15 | } 16 | 17 | //necessary boilerplate, you don't need to implement that if you want a Bloc support level 18 | //interpreter (the easiest && most common) 19 | impl ReplLikeInterpreter for Ada_original {} 20 | 21 | impl Interpreter for Ada_original { 22 | fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box { 23 | //create a subfolder in the cache folder 24 | let awd = data.work_dir.clone() + "/ada_original"; 25 | let mut builder = DirBuilder::new(); 26 | builder.recursive(true); 27 | builder 28 | .create(&awd) 29 | .expect("Could not create directory for example"); 30 | 31 | //pre-create string pointing to main file's and binary's path 32 | let mfp = awd.clone() + "/main.adb"; 33 | let bp = awd.clone() + "/main"; // remove extension so binary is named 'main' 34 | Box::new(Ada_original { 35 | data, 36 | support_level, 37 | code: String::new(), 38 | ada_work_dir: awd, 39 | bin_path: bp, 40 | main_file_path: mfp, 41 | }) 42 | } 43 | 44 | fn get_supported_languages() -> Vec { 45 | vec![ 46 | String::from("Ada"), // in 1st position of vector, used for info only 47 | String::from("ada"), 48 | ] 49 | } 50 | 51 | fn get_name() -> String { 52 | // get your interpreter name 53 | String::from("Ada_original") 54 | } 55 | 56 | fn get_current_level(&self) -> SupportLevel { 57 | self.support_level 58 | } 59 | fn set_current_level(&mut self, level: SupportLevel) { 60 | self.support_level = level; 61 | } 62 | 63 | fn get_data(&self) -> DataHolder { 64 | self.data.clone() 65 | } 66 | 67 | fn get_max_support_level() -> SupportLevel { 68 | SupportLevel::Line 69 | } 70 | 71 | fn default_for_filetype() -> bool { 72 | true 73 | } 74 | 75 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 76 | if !self 77 | .data 78 | .current_bloc 79 | .replace(&[' ', '\t', '\n', '\r'][..], "") 80 | .is_empty() 81 | && self.support_level >= SupportLevel::Bloc 82 | { 83 | self.code.clone_from(&self.data.current_bloc); 84 | } else if !self.data.current_line.replace(' ', "").is_empty() 85 | && self.support_level >= SupportLevel::Line 86 | { 87 | self.code.clone_from(&self.data.current_line); 88 | } else { 89 | self.code = String::from(""); 90 | } 91 | Ok(()) 92 | } 93 | 94 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 95 | self.code = 96 | String::from("with Ada.Text_IO;\nuse Ada.Text_IO;\nprocedure main is\n\nbegin\n") 97 | + &self.code 98 | + "\nend main;"; 99 | Ok(()) 100 | } 101 | 102 | fn build(&mut self) -> Result<(), SniprunError> { 103 | //write code to file 104 | let mut _file = 105 | File::create(&self.main_file_path).expect("Failed to create file for language_subname"); 106 | write(&self.main_file_path, &self.code) 107 | .expect("Unable to write to file for language_subname"); 108 | 109 | let compiler = Ada_original::get_compiler_or(&self.data, "gnatmake"); 110 | let output = Command::new(compiler.split_whitespace().next().unwrap()) 111 | .args(compiler.split_whitespace().skip(1)) 112 | .arg("main") 113 | .arg(&self.main_file_path) 114 | .current_dir(&self.ada_work_dir) 115 | .output() 116 | .expect("Unable to start process"); 117 | if !output.status.success() { 118 | return Err(SniprunError::CompilationError( 119 | String::from_utf8(output.stderr).unwrap(), 120 | )); 121 | } 122 | 123 | Ok(()) 124 | } 125 | 126 | fn execute(&mut self) -> Result { 127 | let output = Command::new(&self.bin_path) 128 | .output() 129 | .expect("Unable to start process"); 130 | 131 | if output.status.success() { 132 | //return stdout 133 | Ok(String::from_utf8(output.stdout).unwrap()) 134 | } else { 135 | // return stderr 136 | Err(SniprunError::RuntimeError( 137 | String::from_utf8(output.stderr).unwrap(), 138 | )) 139 | } 140 | } 141 | } 142 | 143 | // You can add tests if you want to 144 | #[cfg(test)] 145 | mod test_ada_original { 146 | use super::*; 147 | use serial_test::serial; 148 | 149 | #[test] 150 | #[serial(ada)] 151 | fn simple_print() { 152 | let mut data = DataHolder::new(); 153 | 154 | data.current_line = String::from("Put_Line(\"Hi\");"); 155 | let mut interpreter = Ada_original::new(data); 156 | let res = interpreter.run(); 157 | 158 | // -> should panic if not an Ok() 159 | let string_result = res.unwrap(); 160 | 161 | // -> compare result with predicted 162 | assert_eq!(string_result, "Hi\n"); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/interpreters/Bash_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[allow(non_camel_case_types)] 4 | pub struct Bash_original { 5 | support_level: SupportLevel, 6 | data: DataHolder, 7 | code: String, 8 | main_file_path: String, 9 | } 10 | 11 | impl ReplLikeInterpreter for Bash_original {} 12 | impl Interpreter for Bash_original { 13 | fn new_with_level(data: DataHolder, level: SupportLevel) -> Box { 14 | let bwd = data.work_dir.clone() + "/bash-original"; 15 | let mut builder = DirBuilder::new(); 16 | builder.recursive(true); 17 | builder 18 | .create(&bwd) 19 | .expect("Could not create directory for bash-original"); 20 | let mfp = bwd + "/main.sh"; 21 | Box::new(Bash_original { 22 | data, 23 | support_level: level, 24 | code: String::from(""), 25 | main_file_path: mfp, 26 | }) 27 | } 28 | 29 | fn get_name() -> String { 30 | String::from("Bash_original") 31 | } 32 | 33 | fn behave_repl_like_default() -> bool { 34 | false 35 | } 36 | fn has_repl_capability() -> bool { 37 | true 38 | } 39 | 40 | fn default_for_filetype() -> bool { 41 | true 42 | } 43 | 44 | fn check_cli_args(&self) -> Result<(), SniprunError> { 45 | // All cli arguments are sendable to python 46 | // Though they will be ignored in REPL mode 47 | Ok(()) 48 | } 49 | 50 | fn get_supported_languages() -> Vec { 51 | vec![ 52 | String::from("Bash / Shell"), 53 | String::from("bash"), 54 | String::from("shell"), 55 | String::from("sh"), 56 | ] 57 | } 58 | 59 | fn get_current_level(&self) -> SupportLevel { 60 | self.support_level 61 | } 62 | fn set_current_level(&mut self, level: SupportLevel) { 63 | self.support_level = level; 64 | } 65 | 66 | fn get_data(&self) -> DataHolder { 67 | self.data.clone() 68 | } 69 | 70 | fn get_max_support_level() -> SupportLevel { 71 | SupportLevel::Bloc 72 | } 73 | 74 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 75 | if !self 76 | .data 77 | .current_bloc 78 | .replace(&[' ', '\t', '\n', '\r'][..], "") 79 | .is_empty() 80 | && self.get_current_level() >= SupportLevel::Bloc 81 | { 82 | self.code.clone_from(&self.data.current_bloc); 83 | } else if !self.data.current_line.replace(' ', "").is_empty() 84 | && self.get_current_level() >= SupportLevel::Line 85 | { 86 | self.code.clone_from(&self.data.current_line); 87 | } else { 88 | self.code = String::from(""); 89 | } 90 | Ok(()) 91 | } 92 | 93 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 94 | //add shebang just in case 95 | self.code = String::from("#!/usr/bin/env bash \n") 96 | + "sniprun_main123456789(){\n" 97 | + &self.code 98 | + "\n}\n" 99 | + "sniprun_main123456789\n"; 100 | Ok(()) 101 | } 102 | 103 | fn build(&mut self) -> Result<(), SniprunError> { 104 | let mut _file = 105 | File::create(&self.main_file_path).expect("Failed to create file for bash-original"); 106 | 107 | write(&self.main_file_path, &self.code).expect("Unable to write to file for bash-original"); 108 | Ok(()) 109 | } 110 | 111 | fn execute(&mut self) -> Result { 112 | let interpreter = Bash_original::get_interpreter_or(&self.data, "bash"); 113 | let output = Command::new(interpreter.split_whitespace().next().unwrap()) 114 | .args(interpreter.split_whitespace().skip(1)) 115 | .arg(&self.main_file_path) 116 | .args(&self.get_data().cli_args) 117 | .output() 118 | .expect("Unable to start process"); 119 | if output.status.success() { 120 | Ok(String::from_utf8(output.stdout).unwrap()) 121 | } else if Bash_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 122 | Err(SniprunError::RuntimeError( 123 | String::from_utf8(output.stderr.clone()) 124 | .unwrap() 125 | .lines() 126 | .next() 127 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 128 | .to_owned(), 129 | )) 130 | } else { 131 | Err(SniprunError::RuntimeError( 132 | String::from_utf8(output.stderr).unwrap(), 133 | )) 134 | } 135 | } 136 | } 137 | 138 | #[cfg(test)] 139 | mod test_bash_original { 140 | use super::*; 141 | 142 | #[test] 143 | fn simple_print() { 144 | let mut data = DataHolder::new(); 145 | data.current_bloc = String::from("A=2 && echo $A"); 146 | let mut interpreter = Bash_original::new(data); 147 | let res = interpreter.run(); 148 | 149 | // should panic if not an Ok() 150 | let string_result = res.unwrap(); 151 | assert_eq!(string_result, "2\n"); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/interpreters/CS_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | #[derive(Clone)] 3 | #[allow(non_camel_case_types)] 4 | pub struct CS_original { 5 | support_level: SupportLevel, 6 | data: DataHolder, 7 | code: String, 8 | main_file_path: String, 9 | } 10 | impl ReplLikeInterpreter for CS_original {} 11 | impl Interpreter for CS_original { 12 | fn new_with_level(data: DataHolder, level: SupportLevel) -> Box { 13 | let bwd = data.work_dir.clone() + "/cs-original"; 14 | let mut builder = DirBuilder::new(); 15 | builder.recursive(true); 16 | builder 17 | .create(&bwd) 18 | .expect("Could not create directory for cs-original"); 19 | let mfp = bwd + "/main.coffee"; 20 | Box::new(CS_original { 21 | data, 22 | support_level: level, 23 | code: String::from(""), 24 | main_file_path: mfp, 25 | }) 26 | } 27 | 28 | fn get_name() -> String { 29 | String::from("CS_original") 30 | } 31 | 32 | fn get_supported_languages() -> Vec { 33 | vec![ 34 | String::from("CoffeeScript"), 35 | String::from("cs"), 36 | String::from("coffeescript"), 37 | String::from("coffee"), 38 | ] 39 | } 40 | 41 | fn get_current_level(&self) -> SupportLevel { 42 | self.support_level 43 | } 44 | fn set_current_level(&mut self, level: SupportLevel) { 45 | self.support_level = level; 46 | } 47 | 48 | fn default_for_filetype() -> bool { 49 | true 50 | } 51 | fn get_data(&self) -> DataHolder { 52 | self.data.clone() 53 | } 54 | 55 | fn get_max_support_level() -> SupportLevel { 56 | SupportLevel::Bloc 57 | } 58 | 59 | fn check_cli_args(&self) -> Result<(), SniprunError> { 60 | // All cli arguments are sendable to python 61 | // Though they will be ignored in REPL mode 62 | Ok(()) 63 | } 64 | 65 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 66 | if !self 67 | .data 68 | .current_bloc 69 | .replace(&[' ', '\t', '\n', '\r'][..], "") 70 | .is_empty() 71 | && self.get_current_level() >= SupportLevel::Bloc 72 | { 73 | self.code.clone_from(&self.data.current_bloc); 74 | } else if !self.data.current_line.replace(' ', "").is_empty() 75 | && self.get_current_level() >= SupportLevel::Line 76 | { 77 | } else { 78 | self.code = String::from(""); 79 | } 80 | Ok(()) 81 | } 82 | 83 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 84 | Ok(()) 85 | } 86 | 87 | fn build(&mut self) -> Result<(), SniprunError> { 88 | let mut _file = 89 | File::create(&self.main_file_path).expect("Failed to create file for cs-original"); 90 | 91 | write(&self.main_file_path, &self.code).expect("Unable to write to file for cs-original"); 92 | Ok(()) 93 | } 94 | 95 | fn execute(&mut self) -> Result { 96 | let interpreter = CS_original::get_interpreter_or(&self.data, "coffee"); 97 | let output = Command::new(interpreter.split_whitespace().next().unwrap()) 98 | .args(interpreter.split_whitespace().skip(1)) 99 | .arg(&self.main_file_path) 100 | .args(&self.get_data().cli_args) 101 | .output() 102 | .expect("Unable to start process"); 103 | info!("yay from cs interpreter"); 104 | if output.status.success() { 105 | Ok(String::from_utf8(output.stdout).unwrap()) 106 | } else if CS_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 107 | return Err(SniprunError::RuntimeError( 108 | String::from_utf8(output.stderr.clone()) 109 | .unwrap() 110 | .lines() 111 | .next() 112 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 113 | .to_owned(), 114 | )); 115 | } else { 116 | return Err(SniprunError::RuntimeError( 117 | String::from_utf8(output.stderr).unwrap(), 118 | )); 119 | } 120 | } 121 | } 122 | 123 | #[cfg(test)] 124 | mod test_cs_original { 125 | use super::*; 126 | use serial_test::serial; 127 | 128 | #[test] 129 | #[serial(cs_original)] 130 | fn simple_print() { 131 | let mut data = DataHolder::new(); 132 | data.current_bloc = String::from("console.log(\"helo\")"); 133 | let mut interpreter = CS_original::new(data); 134 | let res = interpreter.run(); 135 | 136 | // should panic if not an Ok() 137 | let string_result = res.unwrap(); 138 | assert_eq!(string_result, "helo\n"); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/interpreters/CSharp_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct CSharp_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | 10 | ///specific to csharp 11 | compiler: String, 12 | bin_path: String, 13 | main_file_path: String, 14 | } 15 | 16 | impl CSharp_original { 17 | fn fetch_config(&mut self) { 18 | let default_compiler = String::from("csc"); 19 | self.compiler = default_compiler; 20 | if let Some(used_compiler) = 21 | CSharp_original::get_interpreter_option(&self.get_data(), "compiler") 22 | { 23 | if let Some(compiler_string) = used_compiler.as_str() { 24 | info!("Using custom compiler: {}", compiler_string); 25 | self.compiler = compiler_string.to_string(); 26 | } 27 | } 28 | } 29 | } 30 | 31 | impl ReplLikeInterpreter for CSharp_original {} 32 | impl Interpreter for CSharp_original { 33 | fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box { 34 | //create a subfolder in the cache folder 35 | let rwd = data.work_dir.clone() + "/csharp_original"; 36 | let mut builder = DirBuilder::new(); 37 | builder.recursive(true); 38 | builder 39 | .create(&rwd) 40 | .expect("Could not create directory for csharp-original"); 41 | 42 | //pre-create string pointing to main file's and binary's path 43 | let mfp = rwd + "/main.cs"; 44 | let bp = String::from(&mfp[..mfp.len() - 3]); // remove extension so binary is named 'main' 45 | Box::new(CSharp_original { 46 | data, 47 | support_level, 48 | code: String::new(), 49 | bin_path: bp, 50 | main_file_path: mfp, 51 | compiler: String::new(), 52 | }) 53 | } 54 | 55 | fn get_supported_languages() -> Vec { 56 | vec![ 57 | String::from("C#"), 58 | String::from("csharp"), 59 | String::from("cs"), 60 | ] 61 | } 62 | 63 | fn get_name() -> String { 64 | String::from("CSharp_original") 65 | } 66 | 67 | fn default_for_filetype() -> bool { 68 | true 69 | } 70 | fn get_current_level(&self) -> SupportLevel { 71 | self.support_level 72 | } 73 | fn set_current_level(&mut self, level: SupportLevel) { 74 | self.support_level = level; 75 | } 76 | 77 | fn get_data(&self) -> DataHolder { 78 | self.data.clone() 79 | } 80 | 81 | fn get_max_support_level() -> SupportLevel { 82 | SupportLevel::Bloc 83 | } 84 | 85 | fn check_cli_args(&self) -> Result<(), SniprunError> { 86 | // All cli arguments are sendable to Csharp 87 | Ok(()) 88 | } 89 | 90 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 91 | self.fetch_config(); 92 | //add code from data to self.code 93 | if !self 94 | .data 95 | .current_bloc 96 | .replace(&[' ', '\t', '\n', '\r'][..], "") 97 | .is_empty() 98 | && self.support_level >= SupportLevel::Bloc 99 | { 100 | self.code.clone_from(&self.data.current_bloc); 101 | } else if !self.data.current_line.replace(' ', "").is_empty() 102 | && self.support_level >= SupportLevel::Line 103 | { 104 | self.code.clone_from(&self.data.current_line); 105 | } else { 106 | self.code = String::from(""); 107 | } 108 | Ok(()) 109 | } 110 | 111 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 112 | if !CSharp_original::contains_main("static void Main(string[] args)", &self.code, "//") { 113 | self.code = 114 | String::from("using System; class Hello { static void Main(string[] args) {\n ") 115 | + &self.code 116 | + "\n} }"; 117 | } 118 | 119 | if !CSharp_original::contains_main("using System", &self.code, "//") { 120 | self.code = String::from("using System;\n") + &self.code; 121 | } 122 | Ok(()) 123 | } 124 | 125 | fn build(&mut self) -> Result<(), SniprunError> { 126 | //write code to file 127 | let mut _file = 128 | File::create(&self.main_file_path).expect("Failed to create file for csharp-original"); 129 | write(&self.main_file_path, &self.code) 130 | .expect("Unable to write to file for csharp-original"); 131 | 132 | //compile it (to the bin_path that arleady points to the rigth path) 133 | let output = Command::new(self.compiler.split_whitespace().next().unwrap()) 134 | .args(self.compiler.split_whitespace().skip(1)) 135 | .arg(String::from("-out:") + &self.bin_path) 136 | .arg(&self.main_file_path) 137 | .output() 138 | .expect("Unable to start process"); 139 | 140 | //TODO if relevant, return the error number (parse it from stderr) 141 | if !output.status.success() { 142 | let error_message = String::from_utf8(output.stderr).unwrap(); 143 | //take first line and remove first 'error' word (redondant) 144 | let first_line = error_message 145 | .lines() 146 | .next() 147 | .unwrap_or_default() 148 | .trim_start_matches("error: ") 149 | .trim_start_matches("error"); 150 | Err(SniprunError::CompilationError(first_line.to_owned())) 151 | } else { 152 | Ok(()) 153 | } 154 | } 155 | 156 | fn execute(&mut self) -> Result { 157 | //run th binary and get the std output (or stderr) 158 | let output = Command::new("mono") 159 | .arg(&self.bin_path) 160 | .args(&self.get_data().cli_args) 161 | .output() 162 | .expect("Unable to start process"); 163 | if output.status.success() { 164 | Ok(String::from_utf8(output.stdout).unwrap()) 165 | } else if CSharp_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 166 | return Err(SniprunError::RuntimeError( 167 | String::from_utf8(output.stderr.clone()) 168 | .unwrap() 169 | .lines() 170 | .next() 171 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 172 | .to_owned(), 173 | )); 174 | } else { 175 | return Err(SniprunError::RuntimeError( 176 | String::from_utf8(output.stderr).unwrap(), 177 | )); 178 | } 179 | } 180 | } 181 | 182 | #[cfg(test)] 183 | mod test_csharp_original { 184 | use super::*; 185 | 186 | #[test] 187 | fn simple_print() { 188 | let mut data = DataHolder::new(); 189 | data.current_bloc = String::from("Console.WriteLine(\"Hello World!\");"); 190 | let mut interpreter = CSharp_original::new(data); 191 | let res = interpreter.run(); 192 | 193 | // should panic if not an Ok() 194 | let string_result = res.unwrap(); 195 | assert_eq!(string_result, "Hello World!\n"); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/interpreters/D_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct D_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | 10 | main_file_path: String, 11 | } 12 | impl ReplLikeInterpreter for D_original {} 13 | impl Interpreter for D_original { 14 | fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box { 15 | //create a subfolder in the cache folder 16 | let rwd = data.work_dir.clone() + "/d_original"; 17 | let mut builder = DirBuilder::new(); 18 | builder.recursive(true); 19 | builder 20 | .create(&rwd) 21 | .expect("Could not create directory for d-original"); 22 | 23 | //pre-create string pointing to main file's and binary's path 24 | let mfp = rwd + "/main.d"; 25 | Box::new(D_original { 26 | data, 27 | support_level, 28 | code: String::from(""), 29 | main_file_path: mfp, 30 | }) 31 | } 32 | 33 | fn get_supported_languages() -> Vec { 34 | vec![String::from("D"), String::from("d"), String::from("dlang")] 35 | } 36 | 37 | fn get_name() -> String { 38 | String::from("D_original") 39 | } 40 | 41 | fn get_current_level(&self) -> SupportLevel { 42 | self.support_level 43 | } 44 | fn set_current_level(&mut self, level: SupportLevel) { 45 | self.support_level = level; 46 | } 47 | 48 | fn default_for_filetype() -> bool { 49 | true 50 | } 51 | 52 | fn get_data(&self) -> DataHolder { 53 | self.data.clone() 54 | } 55 | 56 | fn get_max_support_level() -> SupportLevel { 57 | SupportLevel::Bloc 58 | } 59 | 60 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 61 | //add code from data to self.code 62 | if !self 63 | .data 64 | .current_bloc 65 | .replace(&[' ', '\t', '\n', '\r'][..], "") 66 | .is_empty() 67 | && self.support_level >= SupportLevel::Bloc 68 | { 69 | self.code.clone_from(&self.data.current_bloc); 70 | } else if !self.data.current_line.replace(' ', "").is_empty() 71 | && self.support_level >= SupportLevel::Line 72 | { 73 | self.code.clone_from(&self.data.current_line); 74 | } else { 75 | self.code = String::from(""); 76 | } 77 | Ok(()) 78 | } 79 | 80 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 81 | self.code = String::from("import std.stdio;\nvoid main() {") + &self.code + "\n}"; 82 | Ok(()) 83 | } 84 | 85 | fn build(&mut self) -> Result<(), SniprunError> { 86 | //write code to file 87 | let mut _file = 88 | File::create(&self.main_file_path).expect("Failed to create file for d-original"); 89 | write(&self.main_file_path, &self.code).expect("Unable to write to file for d-original"); 90 | Ok(()) 91 | } 92 | 93 | fn execute(&mut self) -> Result { 94 | //run th binary and get the std output (or stderr) 95 | let interpreter = D_original::get_interpreter_or(&self.data, "dmd"); 96 | let output = Command::new(interpreter.split_whitespace().next().unwrap()) 97 | .args(interpreter.split_whitespace().skip(1)) 98 | .arg("-run") 99 | .arg(&self.main_file_path) 100 | .output() 101 | .expect("Unable to start process"); 102 | if output.status.success() { 103 | Ok(String::from_utf8(output.stdout).unwrap()) 104 | } else if D_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 105 | Err(SniprunError::RuntimeError( 106 | String::from_utf8(output.stderr.clone()) 107 | .unwrap() 108 | .lines() 109 | .next() 110 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 111 | .to_owned(), 112 | )) 113 | } else { 114 | Err(SniprunError::RuntimeError( 115 | String::from_utf8(output.stderr).unwrap(), 116 | )) 117 | } 118 | } 119 | } 120 | 121 | #[cfg(test)] 122 | mod test_d_original { 123 | use super::*; 124 | use serial_test::serial; 125 | 126 | #[test] 127 | #[serial(d_original)] 128 | fn simple_print() { 129 | let mut data = DataHolder::new(); 130 | data.current_bloc = 131 | String::from("string yourName = \"a\";\nwritefln(\"Hi %s!\", yourName);"); 132 | let mut interpreter = D_original::new(data); 133 | let res = interpreter.run(); 134 | 135 | // should panic if not an Ok() 136 | let string_result = res.unwrap(); 137 | assert_eq!(string_result, "Hi a!\n"); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/interpreters/Haskell_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct Haskell_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | 10 | bin_path: String, 11 | main_file_path: String, 12 | } 13 | impl ReplLikeInterpreter for Haskell_original {} 14 | impl Interpreter for Haskell_original { 15 | fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box { 16 | //create a subfolder in the cache folder 17 | let rwd = data.work_dir.clone() + "/haskell_original"; 18 | let mut builder = DirBuilder::new(); 19 | builder.recursive(true); 20 | builder 21 | .create(&rwd) 22 | .expect("Could not create directory for haskell-original"); 23 | 24 | //pre-create string pointing to main file's and binary's path 25 | let mfp = rwd + "/main.hs"; 26 | let bp = String::from(&mfp[..mfp.len() - 3]); // remove extension so binary is named 'main' 27 | Box::new(Haskell_original { 28 | data, 29 | support_level, 30 | code: String::from(""), 31 | bin_path: bp, 32 | main_file_path: mfp, 33 | }) 34 | } 35 | 36 | fn get_supported_languages() -> Vec { 37 | vec![ 38 | String::from("Haskell"), 39 | String::from("haskell"), 40 | String::from("hs"), 41 | ] 42 | } 43 | 44 | fn get_name() -> String { 45 | String::from("Haskell_original") 46 | } 47 | 48 | fn default_for_filetype() -> bool { 49 | true 50 | } 51 | 52 | fn get_current_level(&self) -> SupportLevel { 53 | self.support_level 54 | } 55 | fn set_current_level(&mut self, level: SupportLevel) { 56 | self.support_level = level; 57 | } 58 | 59 | fn get_data(&self) -> DataHolder { 60 | self.data.clone() 61 | } 62 | 63 | fn get_max_support_level() -> SupportLevel { 64 | SupportLevel::Line 65 | } 66 | 67 | fn check_cli_args(&self) -> Result<(), SniprunError> { 68 | // All cli arguments are sendable to python 69 | // Though they will be ignored in REPL mode 70 | Ok(()) 71 | } 72 | 73 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 74 | //add code from data to self.code 75 | if !self 76 | .data 77 | .current_bloc 78 | .replace(&[' ', '\t', '\n', '\r'][..], "") 79 | .is_empty() 80 | && self.support_level >= SupportLevel::Bloc 81 | { 82 | self.code.clone_from(&self.data.current_bloc); 83 | } else if !self.data.current_line.replace(' ', "").is_empty() 84 | && self.support_level >= SupportLevel::Line 85 | { 86 | self.code.clone_from(&self.data.current_line); 87 | } else { 88 | self.code = String::from(""); 89 | } 90 | Ok(()) 91 | } 92 | 93 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 94 | self.code = String::from("main = ") + &self.code; 95 | Ok(()) 96 | } 97 | 98 | fn build(&mut self) -> Result<(), SniprunError> { 99 | //write code to file 100 | let mut _file = 101 | File::create(&self.main_file_path).expect("Failed to create file for haskell-original"); 102 | write(&self.main_file_path, &self.code) 103 | .expect("Unable to write to file for haskell-original"); 104 | 105 | //compile it (to the bin_path that arleady points to the rigth path) 106 | info!( 107 | "haskell interpreter : main & bin paths are {}, {}", 108 | &self.main_file_path, &self.bin_path 109 | ); 110 | let compiler = Haskell_original::get_compiler_or(&self.data, "ghc"); 111 | let output = Command::new(compiler.split_whitespace().next().unwrap()) 112 | .args(compiler.split_whitespace().skip(1)) 113 | .arg("-dynamic") 114 | .arg("-o") 115 | .arg(self.bin_path.clone()) 116 | .arg(&self.main_file_path) 117 | .output() 118 | .expect("Unable to start process"); 119 | 120 | info!("code : {:?}", &self.code); 121 | //TODO if relevant, return the error number (parse it from stderr) 122 | if !output.status.success() { 123 | Err(SniprunError::CompilationError( 124 | String::from_utf8(output.stderr).unwrap(), 125 | )) 126 | } else { 127 | Ok(()) 128 | } 129 | } 130 | 131 | fn execute(&mut self) -> Result { 132 | //run th binary and get the std output (or stderr) 133 | let output = Command::new(&self.bin_path) 134 | .args(&self.get_data().cli_args) 135 | .output() 136 | .expect("Unable to start process"); 137 | if output.status.success() { 138 | Ok(String::from_utf8(output.stdout).unwrap()) 139 | } else if Haskell_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 140 | Err(SniprunError::RuntimeError( 141 | String::from_utf8(output.stderr.clone()) 142 | .unwrap() 143 | .lines() 144 | .next() 145 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 146 | .to_owned(), 147 | )) 148 | } else { 149 | Err(SniprunError::RuntimeError( 150 | String::from_utf8(output.stderr).unwrap(), 151 | )) 152 | } 153 | } 154 | } 155 | #[cfg(test)] 156 | mod test_haskell_original { 157 | use super::*; 158 | 159 | #[test] 160 | fn simple_print() { 161 | let mut data = DataHolder::new(); 162 | data.current_line = String::from("putStrLn \"Hi\""); 163 | let mut interpreter = Haskell_original::new(data); 164 | let res = interpreter.run(); 165 | 166 | // should panic if not an Ok() 167 | let string_result = res.unwrap(); 168 | assert_eq!(string_result, "Hi\n"); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/interpreters/JS_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct JS_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | main_file_path: String, 10 | } 11 | impl ReplLikeInterpreter for JS_original {} 12 | impl Interpreter for JS_original { 13 | fn new_with_level(data: DataHolder, level: SupportLevel) -> Box { 14 | let bwd = data.work_dir.clone() + "/js-original"; 15 | let mut builder = DirBuilder::new(); 16 | builder.recursive(true); 17 | builder 18 | .create(&bwd) 19 | .expect("Could not create directory for js-original"); 20 | let mfp = bwd + "/main.js"; 21 | Box::new(JS_original { 22 | data, 23 | support_level: level, 24 | code: String::from(""), 25 | main_file_path: mfp, 26 | }) 27 | } 28 | 29 | fn get_name() -> String { 30 | String::from("JS_original") 31 | } 32 | 33 | fn get_supported_languages() -> Vec { 34 | vec![ 35 | String::from("JavaScript"), 36 | String::from("js"), 37 | String::from("javascript"), 38 | ] 39 | } 40 | 41 | fn get_current_level(&self) -> SupportLevel { 42 | self.support_level 43 | } 44 | fn set_current_level(&mut self, level: SupportLevel) { 45 | self.support_level = level; 46 | } 47 | 48 | fn default_for_filetype() -> bool { 49 | true 50 | } 51 | 52 | fn get_data(&self) -> DataHolder { 53 | self.data.clone() 54 | } 55 | 56 | fn check_cli_args(&self) -> Result<(), SniprunError> { 57 | // All cli arguments are sendable to python 58 | // Though they will be ignored in REPL mode 59 | Ok(()) 60 | } 61 | 62 | fn get_max_support_level() -> SupportLevel { 63 | SupportLevel::Bloc 64 | } 65 | 66 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 67 | if !self 68 | .data 69 | .current_bloc 70 | .replace(&[' ', '\t', '\n', '\r'][..], "") 71 | .is_empty() 72 | && self.get_current_level() >= SupportLevel::Bloc 73 | { 74 | self.code.clone_from(&self.data.current_bloc); 75 | } else if !self.data.current_line.replace(' ', "").is_empty() 76 | && self.get_current_level() >= SupportLevel::Line 77 | { 78 | self.code.clone_from(&self.data.current_line); 79 | } else { 80 | self.code = String::from(""); 81 | } 82 | Ok(()) 83 | } 84 | 85 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 86 | Ok(()) 87 | } 88 | 89 | fn build(&mut self) -> Result<(), SniprunError> { 90 | let mut _file = 91 | File::create(&self.main_file_path).expect("Failed to create file for js-original"); 92 | 93 | write(&self.main_file_path, &self.code).expect("Unable to write to file for js-original"); 94 | Ok(()) 95 | } 96 | 97 | fn execute(&mut self) -> Result { 98 | let interpreter = JS_original::get_interpreter_or(&self.data, "node"); 99 | let output = Command::new(interpreter.split_whitespace().next().unwrap()) 100 | .args(interpreter.split_whitespace().skip(1)) 101 | .arg(&self.main_file_path) 102 | .args(&self.get_data().cli_args) 103 | .output() 104 | .expect("Unable to start process"); 105 | info!("yay from js interpreter"); 106 | if output.status.success() { 107 | Ok(String::from_utf8(output.stdout).unwrap()) 108 | } else { 109 | Err(SniprunError::RuntimeError( 110 | String::from_utf8(output.stderr).unwrap(), 111 | )) 112 | } 113 | } 114 | } 115 | 116 | #[cfg(test)] 117 | mod test_js_original { 118 | use super::*; 119 | 120 | use serial_test::serial; 121 | 122 | #[test] 123 | #[serial(js)] 124 | fn simple_print() { 125 | let mut data = DataHolder::new(); 126 | data.current_bloc = String::from("console.log(\"Hello, World!\");"); 127 | let mut interpreter = JS_original::new(data); 128 | let res = interpreter.run(); 129 | 130 | // should panic if not an Ok() 131 | let string_result = res.unwrap(); 132 | assert_eq!(string_result, "Hello, World!\n"); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/interpreters/Java_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct Java_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | 10 | ///specific to java 11 | java_work_dir: String, 12 | bin_name: String, 13 | main_file_path: String, 14 | } 15 | impl ReplLikeInterpreter for Java_original {} 16 | impl Interpreter for Java_original { 17 | fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box { 18 | //create a subfolder in the cache folder 19 | let jwd = data.work_dir.clone() + "/java_original"; 20 | let mut builder = DirBuilder::new(); 21 | builder.recursive(true); 22 | builder 23 | .create(&jwd) 24 | .expect("Could not create directory for java-original"); 25 | 26 | //pre-create string pointing to main file's and binary's path 27 | let mfp = jwd.clone() + "/Main.java"; 28 | let bn = "Main".to_string(); // remove extension so binary is named 'main' 29 | Box::new(Java_original { 30 | data, 31 | support_level, 32 | code: String::from(""), 33 | java_work_dir: jwd, 34 | bin_name: bn, 35 | main_file_path: mfp, 36 | }) 37 | } 38 | 39 | fn get_supported_languages() -> Vec { 40 | vec![String::from("Java"), String::from("java")] 41 | } 42 | 43 | fn get_name() -> String { 44 | String::from("Java_original") 45 | } 46 | 47 | fn get_current_level(&self) -> SupportLevel { 48 | self.support_level 49 | } 50 | fn set_current_level(&mut self, level: SupportLevel) { 51 | self.support_level = level; 52 | } 53 | 54 | fn default_for_filetype() -> bool { 55 | true 56 | } 57 | 58 | fn get_data(&self) -> DataHolder { 59 | self.data.clone() 60 | } 61 | 62 | fn get_max_support_level() -> SupportLevel { 63 | SupportLevel::Bloc 64 | } 65 | 66 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 67 | //add code from data to self.code 68 | if !self 69 | .data 70 | .current_bloc 71 | .replace(&[' ', '\t', '\n', '\r'][..], "") 72 | .is_empty() 73 | && self.support_level >= SupportLevel::Bloc 74 | { 75 | self.code.clone_from(&self.data.current_bloc); 76 | } else if !self.data.current_line.replace(' ', "").is_empty() 77 | && self.support_level >= SupportLevel::Line 78 | { 79 | self.code.clone_from(&self.data.current_line); 80 | } else { 81 | self.code = String::from(""); 82 | } 83 | Ok(()) 84 | } 85 | 86 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 87 | if !Java_original::contains_main("public static void main(", &self.code, "//") { 88 | self.code = String::from( 89 | "public class Main { 90 | public static void main(String[] args) { 91 | ", 92 | ) + &self.code 93 | + "\n} 94 | }"; 95 | } 96 | Ok(()) 97 | } 98 | 99 | fn build(&mut self) -> Result<(), SniprunError> { 100 | //write code to file 101 | let mut _file = 102 | File::create(&self.main_file_path).expect("Failed to create file for java-original"); 103 | write(&self.main_file_path, &self.code).expect("Unable to write to file for java-original"); 104 | 105 | let compiler = Java_original::get_compiler_or(&self.data, "javac"); 106 | //compile it (to the bin_path that arleady points to the rigth path) 107 | let output = Command::new(compiler.split_whitespace().next().unwrap()) 108 | .args(compiler.split_whitespace().skip(1)) 109 | .arg("-d") 110 | .arg(&self.java_work_dir) 111 | .arg(&self.main_file_path) 112 | .output() 113 | .expect("Unable to start process"); 114 | 115 | //TODO if relevant, return the error number (parse it from stderr) 116 | if !output.status.success() { 117 | Err(SniprunError::CompilationError("".to_string())) 118 | } else { 119 | Ok(()) 120 | } 121 | } 122 | 123 | fn execute(&mut self) -> Result { 124 | //run th binary and get the std output (or stderr) 125 | let output = Command::new("java") 126 | .arg("-cp") 127 | .arg(&self.java_work_dir) 128 | .arg(&self.bin_name) 129 | .output() 130 | .expect("Unable to start process"); 131 | if output.status.success() { 132 | Ok(String::from_utf8(output.stdout).unwrap()) 133 | } else if Java_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 134 | Err(SniprunError::RuntimeError( 135 | String::from_utf8(output.stderr.clone()) 136 | .unwrap() 137 | .lines() 138 | .next() 139 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 140 | .to_owned(), 141 | )) 142 | } else { 143 | Err(SniprunError::RuntimeError( 144 | String::from_utf8(output.stderr).unwrap(), 145 | )) 146 | } 147 | } 148 | } 149 | 150 | #[cfg(test)] 151 | mod test_java_original { 152 | use super::*; 153 | 154 | #[test] 155 | fn simple_print() { 156 | let mut data = DataHolder::new(); 157 | data.current_bloc = String::from("System.out.println(\"hello\");"); 158 | let mut interpreter = Java_original::new(data); 159 | let res = interpreter.run(); 160 | 161 | // should panic if not an Ok() 162 | let string_result = res.unwrap(); 163 | assert_eq!(string_result, "hello\n"); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/interpreters/Julia_original/init_repl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # this script takes 3 (or more) args. 4 | # $1 is a path to a working directory 5 | # $2 is the PID of the parent neovim session. All processes created here will be killed when the parent exits 6 | # $3, $4 ... are the repl command to launch (ex: "deno", "repl" "-q") 7 | 8 | 9 | working_dir="$1/fifo_repl" 10 | log=$working_dir/../background_repl_process.log 11 | if test -e "$working_dir" ; then 12 | echo "process already present" >> $log 13 | exit 1 14 | fi 15 | 16 | 17 | 18 | 19 | pipe=pipe_in 20 | out=out_file 21 | err=err_file 22 | 23 | nvim_pid=$2 24 | 25 | shift 2 26 | repl="$@" # the rest 27 | 28 | 29 | rm -rf $working_dir/ 30 | mkdir -p $working_dir 31 | echo "pid of parent neovim session is $nvim_pid" >> $log 32 | echo "setting up things" >> $log 33 | 34 | mkfifo $working_dir/$pipe 35 | touch $working_dir/$out 36 | sleep 36000 > $working_dir/$pipe & 37 | 38 | echo "/bin/cat " $working_dir/$pipe " | " $repl > $working_dir/real_launcher.sh 39 | chmod +x $working_dir/real_launcher.sh 40 | 41 | echo $repl " process started at $(date +"%F %T")." >> $log 42 | bash $working_dir/real_launcher.sh | tee $working_dir/$out 2| tee $working_dir/$err & 43 | 44 | echo "done" >> $log 45 | 46 | 47 | # wait for the parent nvim process to exit, then kill the sniprun process that launched this script 48 | 49 | while ps -p $nvim_pid ;do 50 | sleep 1 51 | done 52 | 53 | 54 | pkill -P $$ 55 | 56 | echo $repl " and other background processes terminated at $(date +"%F %T")." >> $log 57 | 58 | rm -rf $working_dir 59 | -------------------------------------------------------------------------------- /src/interpreters/Lua_nvim.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::zombie_processes)] 2 | use crate::interpreters::import::*; 3 | 4 | #[derive(Clone)] 5 | #[allow(non_camel_case_types)] 6 | pub struct Lua_nvim { 7 | support_level: SupportLevel, 8 | data: DataHolder, 9 | code: String, 10 | main_file_path: String, 11 | } 12 | impl Interpreter for Lua_nvim { 13 | fn new_with_level(data: DataHolder, level: SupportLevel) -> Box { 14 | let bwd = data.work_dir.clone() + "/nvim-lua"; 15 | let mut builder = DirBuilder::new(); 16 | builder.recursive(true); 17 | builder 18 | .create(&bwd) 19 | .expect("Could not create directory for lua-nvim"); 20 | let mfp = bwd + "/main.lua"; 21 | Box::new(Lua_nvim { 22 | data, 23 | support_level: level, 24 | code: String::from(""), 25 | main_file_path: mfp, 26 | }) 27 | } 28 | 29 | fn behave_repl_like_default() -> bool { 30 | true 31 | } 32 | 33 | fn has_repl_capability() -> bool { 34 | true 35 | } 36 | 37 | fn get_name() -> String { 38 | String::from("Lua_nvim") 39 | } 40 | 41 | fn get_supported_languages() -> Vec { 42 | vec![String::from("Lua"), String::from("lua")] 43 | } 44 | 45 | fn get_current_level(&self) -> SupportLevel { 46 | self.support_level 47 | } 48 | fn set_current_level(&mut self, level: SupportLevel) { 49 | self.support_level = level; 50 | } 51 | 52 | fn get_data(&self) -> DataHolder { 53 | self.data.clone() 54 | } 55 | 56 | fn get_max_support_level() -> SupportLevel { 57 | SupportLevel::Bloc 58 | } 59 | 60 | fn fallback(&mut self) -> Option> { 61 | //do not fallback if one's is explicitely selected 62 | if self.support_level == SupportLevel::Selected { 63 | return None; 64 | } 65 | 66 | self.fetch_code().expect("could not fetch code"); 67 | if !(self.code.contains("nvim") || self.code.contains("vim")) { 68 | //then this is not lua_nvim code but pure lua one 69 | //that doesn't work in nvim context for some reason 70 | // note that since Lua_original is the default and if lua_nvim is selected, we should 71 | // never take this code path 72 | let mut good_interpreter = 73 | crate::interpreters::Lua_original::Lua_original::new_with_level( 74 | self.data.clone(), 75 | SupportLevel::Selected, //prevent fallback infinite loop 76 | ); 77 | return Some(good_interpreter.run()); 78 | } 79 | None 80 | } 81 | 82 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 83 | if !self 84 | .data 85 | .current_bloc 86 | .replace(&[' ', '\t', '\n', '\r'][..], "") 87 | .is_empty() 88 | && self.get_current_level() >= SupportLevel::Bloc 89 | { 90 | self.code.clone_from(&self.data.current_bloc); 91 | } else if !self.data.current_line.replace(' ', "").is_empty() 92 | && self.get_current_level() >= SupportLevel::Line 93 | { 94 | self.code.clone_from(&self.data.current_line); 95 | } else { 96 | self.code = String::from(""); 97 | } 98 | Ok(()) 99 | } 100 | 101 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 102 | let code = self 103 | .code 104 | .lines() 105 | .map(|line| line.strip_prefix("local ").unwrap_or(line)) 106 | .collect::>() 107 | .join("\n"); 108 | self.code = code; 109 | Ok(()) 110 | } 111 | 112 | fn build(&mut self) -> Result<(), SniprunError> { 113 | let mut _file = 114 | File::create(&self.main_file_path).expect("Failed to create file for lua-nvim"); 115 | 116 | write(&self.main_file_path, &self.code).expect("Unable to write to file for lua-nvim"); 117 | Ok(()) 118 | } 119 | 120 | fn execute(&mut self) -> Result { 121 | let interpreter = Lua_nvim::get_interpreter_or(&self.data, "nvim"); 122 | let output = Command::new(interpreter) 123 | .arg("--headless") 124 | .arg("-c") 125 | .arg(format!("luafile {}", &self.main_file_path)) 126 | .arg("-c") 127 | .arg("q!") 128 | .output() 129 | .expect("Unable to start process"); 130 | info!("yay from lua interpreter - in another nvim instance"); 131 | if output.status.success() { 132 | Ok(String::from_utf8(output.stdout).unwrap()) 133 | } else if Lua_nvim::error_truncate(&self.get_data()) == ErrTruncate::Short { 134 | Err(SniprunError::RuntimeError( 135 | String::from_utf8(output.stderr.clone()) 136 | .unwrap() 137 | .lines() 138 | .next() 139 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 140 | .to_owned(), 141 | )) 142 | } else { 143 | Err(SniprunError::RuntimeError( 144 | String::from_utf8(output.stderr).unwrap(), 145 | )) 146 | } 147 | } 148 | } 149 | impl ReplLikeInterpreter for Lua_nvim { 150 | fn fetch_code_repl(&mut self) -> Result<(), SniprunError> { 151 | self.fetch_code() 152 | } 153 | fn add_boilerplate_repl(&mut self) -> Result<(), SniprunError> { 154 | self.add_boilerplate() 155 | } 156 | fn build_repl(&mut self) -> Result<(), SniprunError> { 157 | self.build() 158 | } 159 | fn execute_repl(&mut self) -> Result { 160 | // if current nvim instance is available, execute there 161 | if let Some(real_nvim_instance) = self.data.nvim_instance.clone() { 162 | info!("yay from lua interpreter - in current nvim instance"); 163 | let command_nvim = String::from("luafile ") + &self.main_file_path; 164 | let res = real_nvim_instance 165 | .lock() 166 | .unwrap() 167 | .command_output(&command_nvim); 168 | info!("res : {:?}", res); 169 | match res { 170 | Ok(message) => Ok(message), 171 | Err(e) => Err(SniprunError::RuntimeError(format!("{}", e))), 172 | } 173 | } else { 174 | Err(SniprunError::CustomError(String::from( 175 | "Failed to connect to the current nvim instance", 176 | ))) 177 | } 178 | } 179 | } 180 | 181 | #[cfg(test)] 182 | mod test_lua_nvim { 183 | use super::*; 184 | 185 | #[test] 186 | #[should_panic] 187 | fn run_all() { 188 | //nececssary to run sequentially 189 | //because of file access & shared things 190 | simple_print(); 191 | } 192 | 193 | fn simple_print() { 194 | let mut data = DataHolder::new(); 195 | data.current_bloc = String::from("print(\"Hi\") --nvim vim"); 196 | let mut interpreter = Lua_nvim::new(data); 197 | let res = interpreter.run(); 198 | 199 | // should panic if not an Ok() 200 | let string_result = res.unwrap(); 201 | assert_eq!(string_result, "Hi\n"); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/interpreters/Lua_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct Lua_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | main_file_path: String, 10 | } 11 | impl ReplLikeInterpreter for Lua_original {} 12 | impl Interpreter for Lua_original { 13 | fn new_with_level(data: DataHolder, level: SupportLevel) -> Box { 14 | let bwd = data.work_dir.clone() + "/lua-original"; 15 | let mut builder = DirBuilder::new(); 16 | builder.recursive(true); 17 | builder 18 | .create(&bwd) 19 | .expect("Could not create directory for lua-original"); 20 | let mfp = bwd + "/main.lua"; 21 | Box::new(Lua_original { 22 | data, 23 | support_level: level, 24 | code: String::from(""), 25 | main_file_path: mfp, 26 | }) 27 | } 28 | 29 | fn get_name() -> String { 30 | String::from("Lua_original") 31 | } 32 | 33 | fn get_supported_languages() -> Vec { 34 | vec![String::from("Lua"), String::from("lua")] 35 | } 36 | 37 | fn get_current_level(&self) -> SupportLevel { 38 | self.support_level 39 | } 40 | 41 | fn set_current_level(&mut self, level: SupportLevel) { 42 | self.support_level = level; 43 | } 44 | 45 | fn check_cli_args(&self) -> Result<(), SniprunError> { 46 | // All cli arguments are sendable to python 47 | // Though they will be ignored in REPL mode 48 | Ok(()) 49 | } 50 | 51 | fn default_for_filetype() -> bool { 52 | true 53 | } 54 | 55 | fn get_data(&self) -> DataHolder { 56 | self.data.clone() 57 | } 58 | 59 | fn get_max_support_level() -> SupportLevel { 60 | SupportLevel::Bloc 61 | } 62 | 63 | fn fallback(&mut self) -> Option> { 64 | //do not fallback if one's is explicitely selected 65 | if self.support_level == SupportLevel::Selected { 66 | return None; 67 | } 68 | self.fetch_code().expect("could not fetch code"); 69 | if self.code.contains("nvim") || self.code.contains("vim") { 70 | //then this is not pure lua code but lua-nvim one 71 | let mut good_interpreter = crate::interpreters::Lua_nvim::Lua_nvim::new_with_level( 72 | self.data.clone(), 73 | SupportLevel::Selected, //prevent fallbacking from fallback 74 | ); 75 | return Some(good_interpreter.run()); 76 | } 77 | None 78 | } 79 | 80 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 81 | if !self 82 | .data 83 | .current_bloc 84 | .replace(&[' ', '\t', '\n', '\r'][..], "") 85 | .is_empty() 86 | && self.get_current_level() >= SupportLevel::Bloc 87 | { 88 | self.code.clone_from(&self.data.current_bloc); 89 | } else if !self.data.current_line.replace(' ', "").is_empty() 90 | && self.get_current_level() >= SupportLevel::Line 91 | { 92 | self.code.clone_from(&self.data.current_line); 93 | } else { 94 | self.code = String::from(""); 95 | } 96 | Ok(()) 97 | } 98 | 99 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 100 | Ok(()) 101 | } 102 | 103 | fn build(&mut self) -> Result<(), SniprunError> { 104 | let mut _file = 105 | File::create(&self.main_file_path).expect("Failed to create file for lua-original"); 106 | 107 | write(&self.main_file_path, &self.code).expect("Unable to write to file for lua-original"); 108 | Ok(()) 109 | } 110 | 111 | fn execute(&mut self) -> Result { 112 | let interpreter = Lua_original::get_interpreter_or(&self.data, "lua"); 113 | let output = Command::new(interpreter.split_whitespace().next().unwrap()) 114 | .args(interpreter.split_whitespace().skip(1)) 115 | .arg(&self.main_file_path) 116 | .args(&self.get_data().cli_args) 117 | .output() 118 | .expect("Unable to start process"); 119 | info!("yay from lua interpreter"); 120 | if output.status.success() { 121 | Ok(String::from_utf8(output.stdout).unwrap()) 122 | } else if Lua_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 123 | Err(SniprunError::RuntimeError( 124 | String::from_utf8(output.stderr.clone()) 125 | .unwrap() 126 | .lines() 127 | .next() 128 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 129 | .to_owned(), 130 | )) 131 | } else { 132 | Err(SniprunError::RuntimeError( 133 | String::from_utf8(output.stderr).unwrap(), 134 | )) 135 | } 136 | } 137 | } 138 | 139 | #[cfg(test)] 140 | mod test_lua_original { 141 | use super::*; 142 | 143 | #[test] 144 | fn simple_print() { 145 | let mut data = DataHolder::new(); 146 | data.current_bloc = String::from("print(\"Hi\")"); 147 | let mut interpreter = Lua_original::new(data); 148 | let res = interpreter.run(); 149 | 150 | // should panic if not an Ok() 151 | let string_result = res.unwrap(); 152 | assert_eq!(string_result, "Hi\n"); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/interpreters/Mathematica_original/init_repl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | working_dir=$1 3 | if [ -z "$working_dir" ]; then 4 | exit 1 5 | fi 6 | 7 | 8 | 9 | pipe=pipe_in 10 | out=out_file 11 | 12 | 13 | rm -rf $working_dir/ 14 | mkdir -p $working_dir 15 | 16 | echo "WolframKernel process started at $(date +"%F %T")." >> $working_dir/log 17 | mkfifo $working_dir/$pipe 18 | touch $working_dir/$out 19 | sleep 36000 > $working_dir/$pipe & 20 | WolframKernel -noprompt < $working_dir/$pipe &> $working_dir/$out & 21 | 22 | echo "done_logged" >> $working_dir/log 23 | 24 | 25 | 26 | 27 | 28 | while pkill -0 nvim ;do 29 | sleep 1 30 | done 31 | 32 | pkill -P $$ 33 | 34 | echo "WolframKernel and other backoung process terminated at $(date +"%F %T")." >> $working_dir/log 35 | 36 | -------------------------------------------------------------------------------- /src/interpreters/Mathematica_original/launcher.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cat $1 > $2 3 | -------------------------------------------------------------------------------- /src/interpreters/Prolog_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct Prolog_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | main_file_path: String, 10 | interpreter: String, 11 | } 12 | impl ReplLikeInterpreter for Prolog_original {} 13 | impl Interpreter for Prolog_original { 14 | fn new_with_level(data: DataHolder, level: SupportLevel) -> Box { 15 | let bwd = data.work_dir.clone() + "/prolog-original"; 16 | let mut builder = DirBuilder::new(); 17 | builder.recursive(true); 18 | builder 19 | .create(&bwd) 20 | .expect("Could not create directory for prolog-original"); 21 | let mfp = bwd + "/main.pl"; 22 | Box::new(Prolog_original { 23 | data, 24 | support_level: level, 25 | code: String::from(""), 26 | main_file_path: mfp, 27 | interpreter: String::new(), 28 | }) 29 | } 30 | fn get_name() -> String { 31 | String::from("Prolog_original") 32 | } 33 | fn get_supported_languages() -> Vec { 34 | vec![String::from("Prolog"), String::from("prolog")] 35 | } 36 | fn get_current_level(&self) -> SupportLevel { 37 | self.support_level 38 | } 39 | fn set_current_level(&mut self, level: SupportLevel) { 40 | self.support_level = level 41 | } 42 | fn default_for_filetype() -> bool { 43 | true 44 | } 45 | fn get_data(&self) -> DataHolder { 46 | self.data.clone() 47 | } 48 | fn check_cli_args(&self) -> Result<(), SniprunError> { 49 | // All cli arguments are sendable to the exe 50 | Ok(()) 51 | } 52 | fn get_max_support_level() -> SupportLevel { 53 | SupportLevel::Bloc 54 | } 55 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 56 | let default_interpreter = String::from("gprolog"); 57 | self.interpreter = default_interpreter; 58 | if let Some(used_interpreter) = 59 | Prolog_original::get_interpreter_option(&self.get_data(), "interpreter") 60 | { 61 | if let Some(interpreter_string) = used_interpreter.as_str() { 62 | info!("Using custom interpreter: {}", interpreter_string); 63 | self.interpreter = interpreter_string.to_string(); 64 | } 65 | } 66 | 67 | if !self 68 | .data 69 | .current_bloc 70 | .replace(&[' ', '\t', '\n', '\r'][..], "") 71 | .is_empty() 72 | && self.get_current_level() >= SupportLevel::Bloc 73 | { 74 | self.code.clone_from(&self.data.current_bloc); 75 | } else if !self.data.current_line.replace(' ', "").is_empty() 76 | && self.get_current_level() >= SupportLevel::Line 77 | { 78 | self.code.clone_from(&self.data.current_line); 79 | } else { 80 | self.code = String::from(""); 81 | } 82 | Ok(()) 83 | } 84 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 85 | Ok(()) 86 | } 87 | fn build(&mut self) -> Result<(), SniprunError> { 88 | let mut _file = 89 | File::create(&self.main_file_path).expect("Failed to create file for prolog-original"); 90 | 91 | write(&self.main_file_path, &self.code) 92 | .expect("Unable to write to file for prolog-original"); 93 | Ok(()) 94 | } 95 | fn execute(&mut self) -> Result { 96 | let output = if self.interpreter != "gprolog" { 97 | Command::new(self.interpreter.clone()) 98 | .arg(&self.main_file_path) 99 | .args(&self.get_data().cli_args) 100 | .output() 101 | .expect("Unable to start process") 102 | } else { 103 | // special case for gprolog which needs the --consult-file arg 104 | Command::new("gprolog") 105 | .arg(String::from("--consult-file")) 106 | .arg(&self.main_file_path) 107 | .args(&self.get_data().cli_args) 108 | .output() 109 | .expect("Unable to start process") 110 | }; 111 | info!("yay from Prolog interpreter"); 112 | if output.status.success() { 113 | Ok(String::from_utf8(output.stdout).unwrap()) 114 | } else if Prolog_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 115 | return Err(SniprunError::RuntimeError( 116 | String::from_utf8(output.stderr.clone()) 117 | .unwrap() 118 | .lines() 119 | .last() 120 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 121 | .to_owned(), 122 | )); 123 | } else { 124 | return Err(SniprunError::RuntimeError( 125 | String::from_utf8(output.stderr).unwrap(), 126 | )); 127 | } 128 | } 129 | } 130 | #[cfg(test)] 131 | mod test_prolog_original { 132 | use super::*; 133 | 134 | // #[test] 135 | #[allow(dead_code)] 136 | fn simple_print() { 137 | let mut data = DataHolder::new(); 138 | data.current_bloc = String::from(":- write(ok), halt."); 139 | let mut interpreter = Prolog_original::new(data); 140 | let res = interpreter.run(); 141 | 142 | // should panic if not an Ok() 143 | let string_result = res.unwrap(); 144 | assert_eq!(string_result, "ok"); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/interpreters/Python3_original/saveload.py: -------------------------------------------------------------------------------- 1 | # function has a complicated name so it NEVER gets overriden by user's defs 2 | def sniprun142859_save(sniprun_filename='memo', globals_=None): 3 | 4 | globals_ = globals_ or globals() 5 | import klepto 6 | sniprun_dict_to_save = {} 7 | for key, value in globals_.items(): 8 | if not key.startswith('__'): 9 | sniprun_dict_to_save[key] = value 10 | # print('added %s to save' % key) 11 | sniprun_a = klepto.archives.dir_archive(name=sniprun_filename, dict=sniprun_dict_to_save) 12 | sniprun_a.dump() 13 | 14 | 15 | def sniprun142859_load(sniprun_filename='memo', globals_=None): 16 | import klepto 17 | sniprun_b = klepto.archives.dir_archive(sniprun_filename) 18 | for key in sniprun_b.archive.keys(): 19 | sniprun_b.load(key) 20 | # print("loading %s " % key) 21 | globals()[key] = sniprun_b[key] 22 | -------------------------------------------------------------------------------- /src/interpreters/R_original.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::zombie_processes)] 2 | use crate::interpreters::import::*; 3 | 4 | #[derive(Clone)] 5 | #[allow(non_camel_case_types)] 6 | pub struct R_original { 7 | support_level: SupportLevel, 8 | data: DataHolder, 9 | code: String, 10 | r_work_dir: String, 11 | main_file_path: String, 12 | } 13 | impl Interpreter for R_original { 14 | fn new_with_level(data: DataHolder, level: SupportLevel) -> Box { 15 | let bwd = data.work_dir.clone() + "/R-original"; 16 | let mut builder = DirBuilder::new(); 17 | builder.recursive(true); 18 | builder 19 | .create(&bwd) 20 | .expect("Could not create directory for R-original"); 21 | let mfp = bwd.clone() + "/main.r"; 22 | Box::new(R_original { 23 | data, 24 | support_level: level, 25 | code: String::from(""), 26 | r_work_dir: bwd, 27 | main_file_path: mfp, 28 | }) 29 | } 30 | 31 | fn get_name() -> String { 32 | String::from("R_original") 33 | } 34 | 35 | fn get_supported_languages() -> Vec { 36 | vec![String::from("R"), String::from("r")] 37 | } 38 | fn behave_repl_like_default() -> bool { 39 | true 40 | } 41 | fn has_repl_capability() -> bool { 42 | true 43 | } 44 | 45 | fn default_for_filetype() -> bool { 46 | true 47 | } 48 | 49 | fn get_current_level(&self) -> SupportLevel { 50 | self.support_level 51 | } 52 | fn set_current_level(&mut self, level: SupportLevel) { 53 | self.support_level = level; 54 | } 55 | 56 | fn get_data(&self) -> DataHolder { 57 | self.data.clone() 58 | } 59 | 60 | fn check_cli_args(&self) -> Result<(), SniprunError> { 61 | // All cli arguments are sendable to python 62 | // Though they will be ignored in REPL mode 63 | Ok(()) 64 | } 65 | 66 | fn get_max_support_level() -> SupportLevel { 67 | SupportLevel::Bloc 68 | } 69 | 70 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 71 | if !self 72 | .data 73 | .current_bloc 74 | .replace(&[' ', '\t', '\n', '\r'][..], "") 75 | .is_empty() 76 | && self.get_current_level() >= SupportLevel::Bloc 77 | { 78 | self.code.clone_from(&self.data.current_bloc); 79 | } else if !self.data.current_line.replace(' ', "").is_empty() 80 | && self.get_current_level() >= SupportLevel::Line 81 | { 82 | self.code.clone_from(&self.data.current_line); 83 | } else { 84 | self.code = String::from(""); 85 | } 86 | Ok(()) 87 | } 88 | 89 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 90 | Ok(()) 91 | } 92 | 93 | fn build(&mut self) -> Result<(), SniprunError> { 94 | let mut _file = 95 | File::create(&self.main_file_path).expect("Failed to create file for R-original"); 96 | 97 | write(&self.main_file_path, &self.code).expect("Unable to write to file for R-original"); 98 | Ok(()) 99 | } 100 | 101 | fn execute(&mut self) -> Result { 102 | let interpreter = R_original::get_interpreter_or(&self.data, "Rscript"); 103 | let output = Command::new(interpreter.split_whitespace().next().unwrap()) 104 | .args(interpreter.split_whitespace().skip(1)) 105 | .arg(&self.main_file_path) 106 | .args(&self.get_data().cli_args) 107 | .output() 108 | .expect("Unable to start process"); 109 | info!("yay from R interpreter"); 110 | if output.status.success() { 111 | Ok(String::from_utf8(output.stdout).unwrap()) 112 | } else if R_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 113 | Err(SniprunError::RuntimeError( 114 | String::from_utf8(output.stderr.clone()) 115 | .unwrap() 116 | .lines() 117 | .next() 118 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 119 | .to_owned(), 120 | )) 121 | } else { 122 | Err(SniprunError::RuntimeError( 123 | String::from_utf8(output.stderr).unwrap(), 124 | )) 125 | } 126 | } 127 | } 128 | impl ReplLikeInterpreter for R_original { 129 | fn fetch_code_repl(&mut self) -> Result<(), SniprunError> { 130 | self.fetch_code() 131 | } 132 | fn build_repl(&mut self) -> Result<(), SniprunError> { 133 | self.build() 134 | } 135 | fn execute_repl(&mut self) -> Result { 136 | self.execute() 137 | } 138 | 139 | fn add_boilerplate_repl(&mut self) -> Result<(), SniprunError> { 140 | info!("repl mode"); 141 | let mut final_code = String::new(); 142 | 143 | let rdata_path = self.r_work_dir.clone() + "/sniprun.RData"; 144 | 145 | if self.read_previous_code().is_empty() { 146 | //first run 147 | self.save_code(String::from("Not the first R run anymore")); 148 | } else { 149 | // not first run, tell R to load old variables 150 | { 151 | final_code.push_str("load('"); 152 | final_code.push_str(&rdata_path); 153 | final_code.push_str("')"); 154 | } 155 | } 156 | final_code.push('\n'); 157 | final_code.push_str(&self.code); 158 | final_code.push('\n'); 159 | 160 | { 161 | //save state 162 | final_code.push_str("save.image('"); 163 | final_code.push_str(&rdata_path); 164 | final_code.push_str("')"); 165 | } 166 | self.code = final_code; 167 | Ok(()) 168 | } 169 | } 170 | 171 | #[cfg(test)] 172 | mod test_r_original { 173 | use super::*; 174 | use serial_test::serial; 175 | 176 | #[test] 177 | #[serial(r_original)] 178 | fn simple_print() { 179 | let mut data = DataHolder::new(); 180 | data.current_bloc = String::from("print(\"Hi\");"); 181 | let mut interpreter = R_original::new(data); 182 | let res = interpreter.run(); 183 | 184 | // should panic if not an Ok() 185 | let string_result = res.unwrap(); 186 | assert!(string_result.contains("Hi")); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/interpreters/Ruby_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct Ruby_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | main_file_path: String, 10 | } 11 | impl ReplLikeInterpreter for Ruby_original {} 12 | impl Interpreter for Ruby_original { 13 | fn new_with_level(data: DataHolder, level: SupportLevel) -> Box { 14 | let bwd = data.work_dir.clone() + "/ruby-original"; 15 | let mut builder = DirBuilder::new(); 16 | builder.recursive(true); 17 | builder 18 | .create(&bwd) 19 | .expect("Could not create directory for ruby-original"); 20 | let mfp = bwd + "/main.rb"; 21 | Box::new(Ruby_original { 22 | data, 23 | support_level: level, 24 | code: String::from(""), 25 | main_file_path: mfp, 26 | }) 27 | } 28 | 29 | fn get_name() -> String { 30 | String::from("Ruby_original") 31 | } 32 | 33 | fn get_supported_languages() -> Vec { 34 | vec![ 35 | String::from("Ruby"), 36 | String::from("ruby"), 37 | String::from("rb"), 38 | ] 39 | } 40 | 41 | fn default_for_filetype() -> bool { 42 | true 43 | } 44 | 45 | fn get_current_level(&self) -> SupportLevel { 46 | self.support_level 47 | } 48 | fn set_current_level(&mut self, level: SupportLevel) { 49 | self.support_level = level; 50 | } 51 | 52 | fn get_data(&self) -> DataHolder { 53 | self.data.clone() 54 | } 55 | 56 | fn get_max_support_level() -> SupportLevel { 57 | SupportLevel::Bloc 58 | } 59 | 60 | fn check_cli_args(&self) -> Result<(), SniprunError> { 61 | // All cli arguments are sendable to python 62 | // Though they will be ignored in REPL mode 63 | Ok(()) 64 | } 65 | 66 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 67 | if !self 68 | .data 69 | .current_bloc 70 | .replace(&[' ', '\t', '\n', '\r'][..], "") 71 | .is_empty() 72 | && self.get_current_level() >= SupportLevel::Bloc 73 | { 74 | self.code.clone_from(&self.data.current_bloc); 75 | } else if !self.data.current_line.replace(' ', "").is_empty() 76 | && self.get_current_level() >= SupportLevel::Line 77 | { 78 | self.code.clone_from(&self.data.current_line); 79 | } else { 80 | self.code = String::from(""); 81 | } 82 | Ok(()) 83 | } 84 | 85 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 86 | Ok(()) 87 | } 88 | 89 | fn build(&mut self) -> Result<(), SniprunError> { 90 | let mut _file = 91 | File::create(&self.main_file_path).expect("Failed to create file for ruby-original"); 92 | 93 | write(&self.main_file_path, &self.code).expect("Unable to write to file for ruby-original"); 94 | Ok(()) 95 | } 96 | 97 | fn execute(&mut self) -> Result { 98 | let interpreter = Ruby_original::get_interpreter_or(&self.data, "ruby"); 99 | let output = Command::new(interpreter.split_whitespace().next().unwrap()) 100 | .args(interpreter.split_whitespace().skip(1)) 101 | .arg(&self.main_file_path) 102 | .args(&self.get_data().cli_args) 103 | .output() 104 | .expect("Unable to start process"); 105 | info!("yay from ruby interpreter"); 106 | if output.status.success() { 107 | Ok(String::from_utf8(output.stdout).unwrap()) 108 | } else if Ruby_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 109 | Err(SniprunError::RuntimeError( 110 | String::from_utf8(output.stderr.clone()) 111 | .unwrap() 112 | .lines() 113 | .next() 114 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 115 | .to_owned(), 116 | )) 117 | } else { 118 | Err(SniprunError::RuntimeError( 119 | String::from_utf8(output.stderr).unwrap(), 120 | )) 121 | } 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod test_ruby_original { 127 | use super::*; 128 | 129 | #[test] 130 | fn simple_print() { 131 | let mut data = DataHolder::new(); 132 | data.current_bloc = String::from("puts \"hell\""); 133 | let mut interpreter = Ruby_original::new(data); 134 | let res = interpreter.run(); 135 | 136 | // should panic if not an Ok() 137 | let string_result = res.unwrap(); 138 | assert_eq!(string_result, "hell\n"); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/interpreters/SQL_original.rs: -------------------------------------------------------------------------------- 1 | use crate::input; 2 | use crate::interpreters::import::*; 3 | 4 | #[derive(Clone)] 5 | #[allow(non_camel_case_types)] 6 | pub struct SQL_original { 7 | support_level: SupportLevel, 8 | data: DataHolder, 9 | code: String, 10 | 11 | main_file_path: String, 12 | } 13 | impl ReplLikeInterpreter for SQL_original {} 14 | impl Interpreter for SQL_original { 15 | fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box { 16 | //create a subfolder in the cache folder 17 | let rwd = data.work_dir.clone() + "/sql_original"; 18 | let mut builder = DirBuilder::new(); 19 | builder.recursive(true); 20 | builder 21 | .create(&rwd) 22 | .expect("Could not create directory for sql-original"); 23 | 24 | //pre-create string pointing to main file's and binary's path 25 | let mfp = rwd + "/main.sql"; 26 | Box::new(SQL_original { 27 | data, 28 | support_level, 29 | code: String::from(""), 30 | main_file_path: mfp, 31 | }) 32 | } 33 | 34 | fn get_supported_languages() -> Vec { 35 | vec![ 36 | String::from("SQL"), 37 | String::from("sql"), 38 | String::from("usql"), 39 | ] 40 | } 41 | 42 | fn get_name() -> String { 43 | String::from("SQL_original") 44 | } 45 | 46 | fn get_current_level(&self) -> SupportLevel { 47 | self.support_level 48 | } 49 | fn set_current_level(&mut self, level: SupportLevel) { 50 | self.support_level = level; 51 | } 52 | 53 | fn default_for_filetype() -> bool { 54 | true 55 | } 56 | 57 | fn get_data(&self) -> DataHolder { 58 | self.data.clone() 59 | } 60 | 61 | fn get_max_support_level() -> SupportLevel { 62 | SupportLevel::Bloc 63 | } 64 | 65 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 66 | //add code from data to self.code 67 | if !self 68 | .data 69 | .current_bloc 70 | .replace(&[' ', '\t', '\n', '\r'][..], "") 71 | .is_empty() 72 | && self.support_level >= SupportLevel::Bloc 73 | { 74 | self.code.clone_from(&self.data.current_bloc); 75 | } else if !self.data.current_line.replace(' ', "").is_empty() 76 | && self.support_level >= SupportLevel::Line 77 | { 78 | self.code.clone_from(&self.data.current_line); 79 | } else { 80 | self.code = String::from(""); 81 | } 82 | 83 | if self.read_previous_code().is_empty() { 84 | if let Some(nvim_instance) = self.data.nvim_instance.clone() { 85 | let user_input = 86 | input::vim_input_ask("Enter uSQL database address:", &nvim_instance)?; 87 | self.save_code(user_input); 88 | } 89 | } 90 | Ok(()) 91 | } 92 | 93 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 94 | Ok(()) 95 | } 96 | 97 | fn build(&mut self) -> Result<(), SniprunError> { 98 | //write code to file 99 | let mut _file = 100 | File::create(&self.main_file_path).expect("Failed to create file for sql-original"); 101 | write(&self.main_file_path, &self.code).expect("Unable to write to file for sql-original"); 102 | Ok(()) 103 | } 104 | 105 | fn execute(&mut self) -> Result { 106 | //run th binary and get the std output (or stderr) 107 | let interpreter = SQL_original::get_interpreter_or(&self.data, "usql"); 108 | let output = Command::new(interpreter.split_whitespace().next().unwrap()) 109 | .args(interpreter.split_whitespace().skip(1)) 110 | .arg("-w") 111 | .arg("--file") 112 | .arg(&self.main_file_path) 113 | .arg(self.read_previous_code().replace('\n', "")) // contains database address 114 | .current_dir(&self.data.projectroot) 115 | .output() 116 | .expect("Unable to start process"); 117 | if output.status.success() { 118 | Ok(String::from_utf8(output.stdout).unwrap()) 119 | } else if SQL_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 120 | Err(SniprunError::RuntimeError( 121 | String::from_utf8(output.stderr.clone()) 122 | .unwrap() 123 | .lines() 124 | .next() 125 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 126 | .to_owned(), 127 | )) 128 | } else { 129 | Err(SniprunError::RuntimeError( 130 | String::from_utf8(output.stderr).unwrap(), 131 | )) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/interpreters/Scala_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct Scala_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | 10 | ///specific to compiled languages, can be modified of course 11 | language_work_dir: String, 12 | main_file_path: String, 13 | // you can and should add fields as needed 14 | } 15 | 16 | //necessary boilerplate, you don't need to implement that if you want a Bloc support level 17 | //interpreter (the easiest && most common) 18 | impl ReplLikeInterpreter for Scala_original {} 19 | 20 | impl Interpreter for Scala_original { 21 | fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box { 22 | //create a subfolder in the cache folder 23 | let lwd = data.work_dir.clone() + "/scala_original"; 24 | let mut builder = DirBuilder::new(); 25 | builder.recursive(true); 26 | builder 27 | .create(&lwd) 28 | .expect("Could not create directory for example"); 29 | 30 | //pre-create string pointing to main file's and binary's path 31 | let mfp = lwd.clone() + "/Main.scala"; 32 | Box::new(Scala_original { 33 | data, 34 | support_level, 35 | code: String::new(), 36 | language_work_dir: lwd, 37 | main_file_path: mfp, 38 | }) 39 | } 40 | 41 | fn get_supported_languages() -> Vec { 42 | vec![ 43 | String::from("Scala"), // in 1st position of vector, used for info only 44 | String::from("scala"), 45 | ] 46 | } 47 | 48 | fn get_name() -> String { 49 | String::from("Scala_original") 50 | } 51 | 52 | fn default_for_filetype() -> bool { 53 | true 54 | } 55 | 56 | fn get_current_level(&self) -> SupportLevel { 57 | self.support_level 58 | } 59 | fn set_current_level(&mut self, level: SupportLevel) { 60 | self.support_level = level; 61 | } 62 | 63 | fn get_data(&self) -> DataHolder { 64 | self.data.clone() 65 | } 66 | 67 | fn get_max_support_level() -> SupportLevel { 68 | SupportLevel::Bloc 69 | } 70 | 71 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 72 | if !self 73 | .data 74 | .current_bloc 75 | .replace(&[' ', '\t', '\n', '\r'][..], "") 76 | .is_empty() 77 | && self.support_level >= SupportLevel::Bloc 78 | { 79 | // if bloc is not pseudo empty and has Bloc current support level, 80 | // add fetched code to self 81 | self.code.clone_from(&self.data.current_bloc); 82 | 83 | // if there is only data on current line / or Line is the max support level 84 | } else if !self.data.current_line.replace(' ', "").is_empty() 85 | && self.support_level >= SupportLevel::Line 86 | { 87 | self.code.clone_from(&self.data.current_line); 88 | } else { 89 | // no code was retrieved 90 | self.code = String::from(""); 91 | } 92 | info!("scala interpreter fetched code"); 93 | Ok(()) 94 | } 95 | 96 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 97 | // an example following Rust's syntax 98 | 99 | if !Scala_original::contains_main("int main (", &self.code, "//") { 100 | self.code = String::from("object Main {\ndef main(arg: Array[String]) = {") 101 | + &self.code 102 | + "\n}\n}"; 103 | } 104 | Ok(()) 105 | } 106 | 107 | fn build(&mut self) -> Result<(), SniprunError> { 108 | let mut _file = 109 | File::create(&self.main_file_path).expect("Failed to create file for language_subname"); 110 | // IO errors can be ignored, or handled into a proper SniprunError 111 | // If you panic, it should not be too dangerous for anyone 112 | write(&self.main_file_path, &self.code) 113 | .expect("Unable to write to file for language_subname"); 114 | 115 | let compiler = Scala_original::get_compiler_or(&self.data, "scalac"); 116 | //compile it (to the bin_path that arleady points to the rigth path) 117 | let output = Command::new(compiler.split_whitespace().next().unwrap()) 118 | .args(compiler.split_whitespace().skip(1)) 119 | .arg("-d") 120 | .arg(&self.language_work_dir) 121 | .arg(&self.main_file_path) 122 | .output() 123 | .expect("Unable to start process"); 124 | 125 | // if relevant, return the error number (parse it from stderr) 126 | if !output.status.success() { 127 | Err(SniprunError::CompilationError( 128 | String::from_utf8(output.stderr).unwrap(), 129 | )) 130 | } else { 131 | info!("scala compiled successfully"); 132 | Ok(()) 133 | } 134 | } 135 | 136 | fn execute(&mut self) -> Result { 137 | //run th binary and get the std output (or stderr) 138 | let interpreter = Scala_original::get_interpreter_or(&self.data, "scala"); 139 | let output = Command::new(interpreter) 140 | .arg("Main") 141 | .current_dir(&self.language_work_dir) 142 | .output() 143 | .expect("Unable to start process"); 144 | 145 | if output.status.success() { 146 | //return stdout 147 | Ok(String::from_utf8(output.stdout).unwrap()) 148 | } else if Scala_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 149 | Err(SniprunError::RuntimeError( 150 | String::from_utf8(output.stderr.clone()) 151 | .unwrap() 152 | .lines() 153 | .next() 154 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 155 | .to_owned(), 156 | )) 157 | } else { 158 | Err(SniprunError::RuntimeError( 159 | String::from_utf8(output.stderr).unwrap(), 160 | )) 161 | } 162 | } 163 | } 164 | 165 | #[cfg(test)] 166 | mod test_scala_original { 167 | use super::*; 168 | #[test] 169 | fn simple_print() { 170 | let mut data = DataHolder::new(); 171 | 172 | //inspired from Rust syntax 173 | data.current_bloc = String::from("println(\"Hi\")"); 174 | let mut interpreter = Scala_original::new(data); 175 | let res = interpreter.run(); 176 | 177 | let string_result = res.unwrap(); 178 | 179 | // -> compare result with predicted 180 | assert_eq!(string_result, "Hi\n"); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/interpreters/TypeScript_original.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreters::import::*; 2 | 3 | #[derive(Clone)] 4 | #[allow(non_camel_case_types)] 5 | pub struct TypeScript_original { 6 | support_level: SupportLevel, 7 | data: DataHolder, 8 | code: String, 9 | main_file_path: String, 10 | 11 | //specific to Typescript 12 | interpreter: String, 13 | } 14 | 15 | impl ReplLikeInterpreter for TypeScript_original {} 16 | 17 | impl Interpreter for TypeScript_original { 18 | fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box { 19 | //create a subfolder in the cache folder 20 | let lwd = data.work_dir.clone() + "/typescript_original"; 21 | let mut builder = DirBuilder::new(); 22 | builder.recursive(true); 23 | builder 24 | .create(&lwd) 25 | .expect("Could not create directory for example"); 26 | 27 | //pre-create string pointing to main file's and binary's path 28 | let mfp = lwd + "/main.ts"; 29 | 30 | let interpreter = match TypeScript_original::get_interpreter_option(&data, "interpreter") { 31 | Some(user_interpreter) => user_interpreter.to_string().replace("\"", ""), 32 | None => "ts-node".to_string() 33 | }; 34 | Box::new(TypeScript_original { 35 | data, 36 | support_level, 37 | code: String::new(), 38 | main_file_path: mfp, 39 | interpreter 40 | }) 41 | } 42 | 43 | fn get_supported_languages() -> Vec { 44 | vec![ 45 | String::from("TypeScript"), // in 1st position of vector, used for info only 46 | //':set ft?' in nvim to get the filetype of opened file 47 | String::from("typescript"), 48 | String::from("typescriptreact"), 49 | String::from("ts"), //should not be necessary, but just in case 50 | // another similar name (like python and python3)? 51 | ] 52 | } 53 | 54 | fn get_name() -> String { 55 | // get your interpreter name 56 | String::from("TypeScript_original") 57 | } 58 | 59 | fn get_current_level(&self) -> SupportLevel { 60 | self.support_level 61 | } 62 | fn set_current_level(&mut self, level: SupportLevel) { 63 | self.support_level = level; 64 | } 65 | 66 | fn default_for_filetype() -> bool { 67 | true 68 | } 69 | fn get_data(&self) -> DataHolder { 70 | self.data.clone() 71 | } 72 | 73 | fn get_max_support_level() -> SupportLevel { 74 | //define the max level support of the interpreter (see readme for definitions) 75 | SupportLevel::Bloc 76 | } 77 | 78 | fn fetch_code(&mut self) -> Result<(), SniprunError> { 79 | //note: you probably don't have to modify, or even understand this function 80 | 81 | //here if you detect conditions that make higher support level impossible, 82 | //or unecessary, you should set the current level down. Then you will be able to 83 | //ignore maybe-heavy code that won't be needed anyway 84 | 85 | //add code from data to self.code 86 | if !self 87 | .data 88 | .current_bloc 89 | .replace(&[' ', '\t', '\n', '\r'][..], "") 90 | .is_empty() 91 | && self.support_level >= SupportLevel::Bloc 92 | { 93 | self.code.clone_from(&self.data.current_bloc); 94 | } else if !self.data.current_line.replace(' ', "").is_empty() 95 | && self.support_level >= SupportLevel::Line 96 | { 97 | self.code.clone_from(&self.data.current_line); 98 | } else { 99 | // no code was retrieved 100 | self.code = String::from(""); 101 | } 102 | 103 | // now self.code contains the line or bloc of code wanted :-) 104 | info!("Typescript self.code) = {}", self.code); 105 | Ok(()) 106 | } 107 | 108 | fn add_boilerplate(&mut self) -> Result<(), SniprunError> { 109 | Ok(()) 110 | } 111 | 112 | fn build(&mut self) -> Result<(), SniprunError> { 113 | //write code to file 114 | let mut _file = File::create(&self.main_file_path) 115 | .expect("failed to create file for typescript_original"); 116 | // io errors can be ignored, or handled into a proper sniprunerror 117 | // if you panic, it should not be too dangerous for anyone 118 | write(&self.main_file_path, &self.code) 119 | .expect("unable to write to file for typescript_original"); 120 | 121 | Ok(()) 122 | } 123 | 124 | fn execute(&mut self) -> Result { 125 | //run th binary and get the std output (or stderr) 126 | let interpreter = TypeScript_original::get_interpreter_or(&self.data, &self.interpreter); 127 | let output = Command::new(interpreter.split_whitespace().next().unwrap()) 128 | .args(interpreter.split_whitespace().skip(1)) 129 | .arg(&self.main_file_path) 130 | .output() 131 | .expect("Unable to start process"); 132 | 133 | if output.status.success() { 134 | //return stdout 135 | Ok(String::from_utf8(output.stdout).unwrap()) 136 | } else if TypeScript_original::error_truncate(&self.get_data()) == ErrTruncate::Short { 137 | Err(SniprunError::RuntimeError( 138 | String::from_utf8(output.stderr.clone()) 139 | .unwrap() 140 | .lines() 141 | .filter(|l| l.contains("Error:")) 142 | .next_back() 143 | .unwrap_or(&String::from_utf8(output.stderr).unwrap()) 144 | .to_owned(), 145 | )) 146 | } else { 147 | Err(SniprunError::RuntimeError( 148 | String::from_utf8(output.stderr).unwrap(), 149 | )) 150 | } 151 | } 152 | } 153 | 154 | // #[cfg(test)] 155 | // mod test_typescript_original { 156 | // use super::*; 157 | // commenting this, as CI fails with 'invalid token "export"' 158 | // which doesn't happen locally, for some reason 159 | // If an user experiences this and opens an issue i'll probably fix it 160 | // #[test] 161 | // fn simple_print() { 162 | // let mut data = DataHolder::new(); 163 | // 164 | // //inspired from Rust syntax 165 | // data.current_bloc = String::from("let message: string = 'Hi';\nconsole.log(message);"); 166 | // let mut interpreter = TypeScript_original::new(data); 167 | // let res = interpreter.run(); 168 | // 169 | // // -> should panic if not an Ok() 170 | // let string_result = res.unwrap(); 171 | // 172 | // // -> compare result with predicted 173 | // assert_eq!(string_result, "Hi\n"); 174 | // } 175 | // } 176 | -------------------------------------------------------------------------------- /src/interpreters/import.rs: -------------------------------------------------------------------------------- 1 | pub use crate::error::SniprunError; 2 | pub use crate::interpreter::{ 3 | ErrTruncate, Interpreter, InterpreterUtils, ReplLikeInterpreter, SupportLevel, 4 | }; 5 | pub use crate::DataHolder; 6 | pub use log::{debug, error, info, warn}; 7 | 8 | pub use crate::interpreters; 9 | pub use crate::iter_types; 10 | 11 | pub use std::fs::{write, DirBuilder, File}; 12 | pub use std::process::Command; 13 | 14 | pub use neovim_lib::NeovimApi; 15 | 16 | pub use std::env; 17 | 18 | pub use crate::daemonizer::{daemon, Fork}; 19 | 20 | //indentation 21 | pub use unindent::unindent; 22 | 23 | pub use std::io::prelude::*; 24 | 25 | // pub use jupyter_client::Client; 26 | // pub use std::collections::HashMap; 27 | 28 | pub use regex::Regex; 29 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | sniprun::start(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | use sniprun::interpreter::{Interpreter, InterpreterUtils, ReplLikeInterpreter, SupportLevel}; 2 | use sniprun::interpreters::JS_original::JS_original; 3 | use sniprun::*; 4 | use std::sync::{Arc, Mutex}; 5 | 6 | #[test] 7 | fn test_implements() { 8 | let data = DataHolder::new(); 9 | iter_types! { 10 | let mut interpreter = Current::new(data.clone()); 11 | let _ = Current::get_name(); 12 | let _ = Current::default_for_filetype(); 13 | let _ = Current::get_supported_languages(); 14 | let _ = interpreter.fallback(); 15 | let max_level = Current::get_max_support_level(); 16 | let current_level = interpreter.get_current_level(); 17 | assert_eq!(max_level, current_level); 18 | interpreter.set_current_level(SupportLevel::Selected); 19 | assert_eq!(SupportLevel::Selected, interpreter.get_current_level()); 20 | let _ = interpreter.get_data(); 21 | // let _ = interpreter.fallback(); // don't test, this is a 'run' hidden 22 | let _ = Current::behave_repl_like_default(); 23 | let _ = Current::has_repl_capability(); 24 | let _ = Current::has_lsp_capability(); 25 | } 26 | } 27 | 28 | #[test] 29 | fn test_miscellaneous() { 30 | for level in [ 31 | SupportLevel::Unsupported, 32 | SupportLevel::Line, 33 | SupportLevel::Bloc, 34 | SupportLevel::Import, 35 | SupportLevel::File, 36 | SupportLevel::Project, 37 | SupportLevel::Selected, 38 | ] { 39 | println!("{}", level); 40 | } 41 | 42 | let _ = JS_original::get_max_support_level(); 43 | 44 | let data = DataHolder::new(); 45 | let mut i = JS_original::new(data); 46 | assert!(i.fetch_code_repl().is_ok()); 47 | assert!(i.add_boilerplate_repl().is_ok()); 48 | assert!(i.build_repl().is_ok()); 49 | assert!(i.execute_repl().is_err()); 50 | } 51 | 52 | #[test] 53 | fn test_interpreter_utils() { 54 | let mut data = DataHolder::new(); 55 | data.interpreter_data = Some(Arc::new(Mutex::new(InterpreterData { 56 | owner: String::new(), 57 | content: String::new(), 58 | pid: Some(0), 59 | }))); 60 | data.current_bloc = String::from("console.log(\"Hello, World!\");"); 61 | let mut interpreter = JS_original::new(data); 62 | interpreter.save_code(String::from("let a = 3;")); 63 | assert_eq!( 64 | String::from("let a = 3;"), 65 | interpreter.read_previous_code().trim_matches('\n') 66 | ); 67 | interpreter.clear(); 68 | assert!(interpreter.read_previous_code().is_empty()); 69 | 70 | interpreter.set_pid(15); 71 | assert_eq!(Some(15), interpreter.get_pid()); 72 | 73 | // actually run the JS_original interpreter since we highjacked its test 74 | let res = interpreter.run(); 75 | let string_result = res.unwrap(); 76 | assert_eq!(string_result, "Hello, World!\n"); 77 | } 78 | --------------------------------------------------------------------------------