Repository: hkupty/iron.nvim Branch: master Commit: 88cd340407b9 Files: 62 Total size: 86.0 KB Directory structure: gitextract_qzvaewdp/ ├── .busted ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .semaphore/ │ └── semaphore.yml ├── LICENSE ├── README.md ├── bin/ │ └── pctl ├── doc/ │ └── iron.txt ├── lua/ │ └── iron/ │ ├── config.lua │ ├── core.lua │ ├── dap.lua │ ├── debug_level.lua │ ├── fts/ │ │ ├── clojure.lua │ │ ├── common.lua │ │ ├── cpp.lua │ │ ├── csh.lua │ │ ├── elixir.lua │ │ ├── elm.lua │ │ ├── erlang.lua │ │ ├── fennel.lua │ │ ├── fish.lua │ │ ├── forth.lua │ │ ├── haskell.lua │ │ ├── hy.lua │ │ ├── init.lua │ │ ├── janet.lua │ │ ├── javascript.lua │ │ ├── julia.lua │ │ ├── lisp.lua │ │ ├── lua.lua │ │ ├── markdown.lua │ │ ├── mma.lua │ │ ├── ocaml.lua │ │ ├── php.lua │ │ ├── prolog.lua │ │ ├── ps1.lua │ │ ├── pure.lua │ │ ├── python.lua │ │ ├── r.lua │ │ ├── racket.lua │ │ ├── ruby.lua │ │ ├── sbt.lua │ │ ├── scala.lua │ │ ├── scheme.lua │ │ ├── sh.lua │ │ ├── stata.lua │ │ ├── tcl.lua │ │ ├── typescript.lua │ │ └── zsh.lua │ ├── init.lua │ ├── log.lua │ ├── lowlevel.lua │ ├── marks.lua │ ├── providers.lua │ ├── scope.lua │ ├── util/ │ │ ├── os.lua │ │ └── tables.lua │ ├── view.lua │ └── visibility.lua └── spec/ └── iron/ ├── core_spec.lua ├── fts/ │ └── common_spec.lua └── util/ └── tables_spec.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: .busted ================================================ return { default = { verbose = true, lpath = "./lua/?.lua;./lua/?/init.lua" } } ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # [] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .gitignore ================================================ *.py[eco] __pycache__/ Makefile doc/tags ================================================ FILE: .semaphore/semaphore.yml ================================================ version: v1.0 name: Initial Pipeline agent: machine: type: e1-standard-2 os_image: ubuntu2004 blocks: - name: Set-up task: jobs: - name: Lint commands: - checkout - sudo apt-get update - sudo apt-get install -y lua-check - luacheck lua/* --formatter JUnit > ./report.xml epilogue: on_fail: commands: - '[[ -f report.xml ]] && test-results publish report.xml' ================================================ FILE: LICENSE ================================================ BSD 3-Clause License Copyright (c) 2018, Henry John Kupty All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # iron.nvim Interactive Repls Over Neovim. ## What is iron.nvim [![asciicast](https://asciinema.org/a/495376.svg)](https://asciinema.org/a/495376) Iron allows you to quickly interact with the repl without having to leave your work buffer It both a plugin and a library, allowing for better user experience and extensibility at the same time. ## How to install Using [packer.nvim](https://github.com/wbthomason/packer.nvim) (or the plugin manager of your choice): ```lua use {'Vigemus/iron.nvim'} ``` ## How to configure Below is a very simple configuration for iron: ```lua local iron = require("iron.core") local view = require("iron.view") local common = require("iron.fts.common") iron.setup { config = { -- Whether a repl should be discarded or not scratch_repl = true, -- Your repl definitions come here repl_definition = { sh = { -- Can be a table or a function that -- returns a table (see below) command = {"zsh"} }, python = { command = { "python3" }, -- or { "ipython", "--no-autoindent" } format = common.bracketed_paste_python, block_dividers = { "# %%", "#%%" }, env = {PYTHON_BASIC_REPL = "1"} --this is needed for python3.13 and up. } }, -- set the file type of the newly created repl to ft -- bufnr is the buffer id of the REPL and ft is the filetype of the -- language being used for the REPL. repl_filetype = function(bufnr, ft) return ft -- or return a string name such as the following -- return "iron" end, -- Send selections to the DAP repl if an nvim-dap session is running. dap_integration = true, -- How the repl window will be displayed -- See below for more information repl_open_cmd = view.bottom(40), -- repl_open_cmd can also be an array-style table so that multiple -- repl_open_commands can be given. -- When repl_open_cmd is given as a table, the first command given will -- be the command that `IronRepl` initially toggles. -- Moreover, when repl_open_cmd is a table, each key will automatically -- be available as a keymap (see `keymaps` below) with the names -- toggle_repl_with_cmd_1, ..., toggle_repl_with_cmd_k -- For example, -- -- repl_open_cmd = { -- view.split.vertical.rightbelow("%40"), -- cmd_1: open a repl to the right -- view.split.rightbelow("%25") -- cmd_2: open a repl below -- } }, -- Iron doesn't set keymaps by default anymore. -- You can set them here or manually add keymaps to the functions in iron.core keymaps = { toggle_repl = "rr", -- toggles the repl open and closed. -- If repl_open_command is a table as above, then the following keymaps are -- available -- toggle_repl_with_cmd_1 = "rv", -- toggle_repl_with_cmd_2 = "rh", restart_repl = "rR", -- calls `IronRestart` to restart the repl send_motion = "sc", visual_send = "sc", send_file = "sf", send_line = "sl", send_paragraph = "sp", send_until_cursor = "su", send_mark = "sm", send_code_block = "sb", send_code_block_and_move = "sn", mark_motion = "mc", mark_visual = "mc", remove_mark = "md", cr = "s", interrupt = "s", exit = "sq", clear = "cl", }, -- If the highlight is on, you can change how it looks -- For the available options, check nvim_set_hl highlight = { italic = true }, ignore_blank_lines = true, -- ignore blank lines when sending visual select lines } -- iron also has a list of commands, see :h iron-commands for all available commands vim.keymap.set('n', 'rf', 'IronFocus') vim.keymap.set('n', 'rh', 'IronHide') ``` The repl `command` can also be a function: ```lua iron.setup{ config = { repl_definition = { -- custom repl that loads the current file haskell = { command = function(meta) local filename = vim.api.nvim_buf_get_name(meta.current_bufnr) return { 'cabal', 'v2-repl', filename} end } }, }, } ``` ### REPL windows iron.nvim supports both splits and floating windows and has helper functions for opening new repls in either of them: #### For splits If you prefer using splits to your repls, iron provides a few utility functions to make it simpler: ```lua local view = require("iron.view") -- iron.setup {... -- One can always use the default commands from vim directly repl_open_cmd = "vertical botright 80 split" -- But iron provides some utility functions to allow you to declare that dynamically, -- based on editor size or custom logic, for example. -- Vertical 50 columns split -- Split has a metatable that allows you to set up the arguments in a "fluent" API -- you can write as you would write a vim command. -- It accepts: -- - vertical -- - leftabove/aboveleft -- - rightbelow/belowright -- - topleft -- - botright -- They'll return a metatable that allows you to set up the next argument -- or call it with a size parameter repl_open_cmd = view.split.vertical.botright(50) -- If the supplied number is a fraction between 1 and 0, -- it will be used as a proportion repl_open_cmd = view.split.vertical.botright(0.61903398875) -- The size parameter can be a number, a string or a function. -- When it's a *number*, it will be the size in rows/columns -- If it's a *string*, it requires a "%" sign at the end and is calculated -- as a percentage of the editor size -- If it's a *function*, it should return a number for the size of rows/columns repl_open_cmd = view.split("40%") -- You can supply custom logic -- to determine the size of your -- repl's window repl_open_cmd = view.split.topleft(function() if some_check then return vim.o.lines * 0.4 end return 20 end) -- An optional set of options can be given to the split function if one -- wants to configure the window behavior. -- Note that, by default `winfixwidth` and `winfixheight` are set -- to `true`. If you want to overwrite those values, -- you need to specify the keys in the option map as the example below repl_open_cmd = view.split("40%", { winfixwidth = false, winfixheight = false, -- any window-local configuration can be used here number = true }) ``` #### For floats If you prefer floats, the API is the following: ```lua local view = require("iron.view") -- iron.setup {... -- The same size arguments are valid for float functions repl_open_cmd = view.top("10%") -- `view.center` takes either one or two arguments repl_open_cmd = view.center("30%", 20) -- If you supply only one, it will be used for both dimensions -- The function takes an argument to whether the orientation is vertical(true) or -- horizontal (false) repl_open_cmd = view.center(function(vertical) -- Useless function, but it will be called twice, -- once for each dimension (width, height) if vertical then return 50 end return 20 end) -- `view.offset` allows you to control both the size of each dimension and -- the distance of them from the top-left corner of the screen repl_open_cmd = view.offset{ width = 60, height = vim.o.height * 0.75 w_offset = 0, h_offset = "5%" } -- Some helper functions allow you to calculate the offset -- in relation to the size of the window. -- While all other sizing functions take only the orientation boolean (vertical or not), -- for offsets, the functions will also take the repl size in that dimension -- as argument. The helper functions then return a function that takes two arguments -- to calculate the offset repl_open_cmd = view.offset{ width = 60, height = vim.o.height * 0.75 -- `view.helpers.flip` will subtract the size of the REPL -- window from the total dimension, then apply an offset. -- Effectively, it flips the top/left to bottom/right orientation w_offset = view.helpers.flip(2), -- `view.helpers.proportion` allows you to apply a relative -- offset considering the REPL window size. -- for example, 0.5 will centralize the REPL in that dimension, -- 0 will pin it to the top/left and 1 will pin it to the bottom/right. h_offset = view.helpers.proportion(0.5) } -- Differently from `view.center`, all arguments are required -- and no defaults will be applied if something is missing. repl_open_cmd = view.offset{ width = 60, height = vim.o.height * 0.75 -- Since we know we're using this function in the width offset -- calculation, we can ignore the argument w_offset = function(_, repl_size) -- Ideally this function calculates a value based on something.. return 42 end, h_offset = view.helpers.flip(2) } ``` ================================================ FILE: bin/pctl ================================================ #!/bin/bash set -eou pipefail BIN_DIR="$(dirname "$(realpath "$0")")" ROOT_DIR="$(dirname "$BIN_DIR")" LUAROCKS="luarocks-5.1" exists() { command -v "$1" > /dev/null } setup(){ exists luarocks-5.1 || { exists luarocks && { LUAROCKS="luarocks --lua-version 5.1" } || { echo 'Please install luarocks' exit 1 } } $LUAROCKS --local install "$1" } tests(){ exists busted || setup busted busted } linter(){ exists luacheck || setup luacheck luacheck lua/ } run() { tests linter } run-dev(){ inotifywait -r -q -m -e close_write --format %e lua spec | while read -r ; do busted done } cd $ROOT_DIR && "$@" ================================================ FILE: doc/iron.txt ================================================ *iron.nvim* Interactive Repls Over Neovim =============================================================================== CONTENTS *iron-help* Introduction............................|iron-introduction| Awesomeness.............................|iron-awesomeness| Languages...............................|iron-languages| Commands................................|iron-commands| Functions...............................|iron-functions| Customizing.............................|iron-customizing| Mappings................................|iron-mappings| Extending...............................|iron-extending| Credits.................................|iron-credits| =============================================================================== Introduction *iron-introduction* Iron is a helper plugin that allows you to manage and interact with live Read-Eval-Print-Loops (REPLs) directly from Neovim, through terminal buffers. Iron mostly interacts with plugins via stdin/stdout, with few exceptions. =============================================================================== Awesomeness *iron-awesomeness* Iron makes it very easy to focus on the code while interacting with the REPL. It allows you to, seamlessly, send chunks of code directly into the REPL, without disrupting your coding workflow. A lot more can be accomplished through Iron. In order to add new functionality (such as running tests, evaluating expressions, adding new keybindings, etc), all it takes is to extend the REPL providers. =============================================================================== Languages *iron-languages* Currently, Iron has support for the following programming languages: - clojure - boot - lein - cpp - root https://root.cern.ch/ - csh - csh - tcsh - elixir - elm - erlang - fennel - fish - forth - haskell - intero - stack ghci - cabal repl - ghci - hylang - javascript - julia - lisp - sbcl - clisp - lua - ocaml - ocamltop - utop - php - php - psysh - prolog - gprolog - swipl - ps1 (powershell) - pure-lang - python - python - ipython - ptpython - ptipython - r - racket - ruby - scala - scala - sbt - scheme - guile - csi - sh - zsh - bash - sh - stata - tcl - typescript - zsh =============================================================================== Commands *iron-commands* Iron provides the following commands for interacting with REPLs: :IronRepl [ft] Open a REPL for current or given file type. :IronReplHere [ft] Open a REPL for current or given file type in the current window. :IronRestart Restart the current REPL. :IronSend[!] some [text...] Sends the supplied chunk of text to the repl for current filetype. If used with a `!`, the first argument is the filetype This allows for invoking a new repl immediately: > :IronSend! python print(20 * 32) :IronFocus [ft] Focuses on the repl for current or given file type. :IronHide [ft] Hide the repl window for current or given file type. :IronWatch file|mark send the file/mark to the repl after writing the buffer :IronAttach ft Attach current buffer regardless of its filetype to a repl =============================================================================== Functions *iron-functions* Iron is a lua plugin so all its functionality is exposed through lua functions. The code is mainly divided in two major namespaces: - `iron.core`, for most user-facing functions and - `iron.lowlevel` for the functions that interact with the repl directly The code is well documented, so we'll skip `iron.lowlevel` in this doc. Below are the core functions: * core.repl_here(ft) Creates a repl in the same window * core.repl_restart() Restarts the repl in the current window or for the current buffer's filetype * core.close_repl(ft) Closes the repl for supplied filetype * core.repl_for(ft) Creates a repl for given filetype in a new window * core.focus_on(ft) Moves to (or creates) the repl for given filetype * core.send(ft, data) Sends data (a table) to the repl for given filetype * core.send_code_block(move) Sends the lines between two code_dividers as defined in repl_definition or end and start of buffer to the repl. If move is true, the cursor is moved to next code block. If move is false, the cursor position is unchanged. * core.send_file() Sends the whole file to the repl * core.send_line() Sends line below the cursor to the repl * core.send_until_cursor() Sends the buffer from the start until the line where the cursor is (inclusive) to the repl * core.send_motion(motion) Applies given motion and sends the result to the repl. See `send_motion` in |iron-mappings| * core.send_mark() Sends the marked chunk * core.mark_visual() Adds a mark around the visual selection * core.mark_motion() Adds a mark around the motion object * core.watch(handler, bufnr) Watches for saves in the supplied buffer * core.unwatch(bufnr) Removes the watch on the supplied buffer * core.setup(config) Configures iron. See |iron-customizing| For more information, check the luadocs in the functions. =============================================================================== Customizing *iron-customizing* Iron is configured through `core.setup` Below is a sample config: ``` local iron = require("iron.core") iron.setup{ config = { -- Highlights the last sent block with bold highlight_last = "IronLastSent", -- Toggling behavior is on by default. -- Other options are: `single` and `focus` visibility = require("iron.visibility").toggle, -- Scope of the repl -- By default it is one for the same `pwd` -- Other options are `tab_based` and `singleton` scope = require("iron.scope").path_based, -- Whether the repl buffer is a "throwaway" buffer or not scratch_repl = false, -- Automatically closes the repl window on process end close_window_on_exit = true, repl_definition = { -- forcing a default python = require("iron.fts.python").ipython -- new, custom repl lua = { -- Can be a table or a function that returns a table (see below) command = {"my-lua-repl", "-arg"} } -- setting up code_dividers for core.send_code_block python = { command = { "python3" }, -- or { "ipython", "--no-autoindent" } format = require("iron.fts.common").bracketed_paste_python, block_dividers = { "# %%", "#%%" }, } }, -- Whether iron should map the `(..)` mappings should_map_plug = true, -- Repl position. Check `iron.view` for more options, -- currently there are four positions: left, right, bottom, top, -- the param is the width/height of the float window repl_open_cmd = require("iron.view").curry.bottom(40), -- Alternatively, pass a function, which is evaluated when a repl is open. repl_open_cmd = require('iron.view').curry.right(function() return vim.o.columns / 2 end), -- iron.view.curry will open a float window for the REPL. -- alternatively, pass a string of vimscript for opening a fixed window: repl_open_cmd = 'belowright 15 split', -- If the repl buffer is listed buflisted = false, }, -- All the keymaps are set individually -- Below is a suggested default keymaps = { send_motion = "sc", visual_send = "sc", send_file = "sf", send_line = "sl", send_until_cursor = "su", send_mark = "sm", send_code_block = "sb", send_code_block_and_move = "sn", mark_motion = "mc", mark_visual = "mc", remove_mark = "md", cr = "s", interrupt = "s", exit = "sq", clear = "cl", }, -- If the highlight is on, you can change how it looks -- For the available options, check nvim_set_hl highlight = { italic = true } } ``` The repl command can also be a function: ``` iron.setup{ config = { repl_definition = { -- custom repl that loads the current file haskell = { command = function(meta) local filename = vim.api.nvim_buf_get_name(meta.current_bufnr) return { 'cabal', 'v2-repl', filename} end } }, }, } ``` =============================================================================== Mappings *iron-mappings* Iron by default doesn't map the keybindings, only those supplied in the core.setup function. - send_motion: Sends a motion to the repl - visual_send: Sends the visual selection to the repl - send_file: Sends the whole file to the repl - send_line: Sends the line below the cursor to the repl - send_until_cursor: Sends the buffer from the start until the line where the cursor is (inclusive) to the repl - send_mark: Sends the text within the mark - send_code_block: Sends the text between two code dividers - send_code_block_and_move: Sends the text between two code dividers and move to next code block - mark_motion: Marks the text object - mark_visual: Marks the visual selection - remove_mark: Removes the set mark - cr: Sends a return to the repl - interrupt: Sends a `` signal to the repl - exit: Exits the repl - clear: Clears the repl window =============================================================================== Extending *iron-extending* Iron provides some modules that can be used on your configuration to change the behavior: * `iron.memory_management`: It provides three memory management modes for your repls: * `tab_based`: It saves all the variables based on your tab, so new tabs create new repls. * `path_based` (default): It saves all the variables according to your base path (pwd), so changing the cd/tcd/lcd will create new repls. * `singleton`: It will never create two repls for the same filetype. * `iron.visibility`: It changes the behavior on how iron deals with windows for the repl. * `single`: Ensures that a window exists and shows it; * `toggle` (default): Alternates between opening and closing the window * `focus`: Moves the focus to the repl window. * `iron.view`: Creates the windows. Subject to change for the moment. Please refer to the README while the API is still unstable. =============================================================================== Credits *iron-credits* Plugin created by Henry Kupty . It is free to use and extend. Please consider opening a pull request if extended. Source at https://github.com/Vigemus/iron.nvim vim:tw=78:ft=help:norl: ================================================ FILE: lua/iron/config.lua ================================================ -- luacheck: globals vim local view = require("iron.view") --- Default values --@module config local config --- Default configurations for iron.nvim -- @table config.values -- @tfield false|string highlight_last Either false or the name of a highlight group -- @field scratch_repl When enabled, the repl buffer will be a scratch buffer -- @field should_map_plug when enabled iron will provide its mappings as `(..)` as well, -- for backwards compatibility -- @field close_window_on_exit closes repl window on process exit local values = { highlight_last = "IronLastSent", visibility = require("iron.visibility").toggle, scope = require("iron.scope").path_based, scratch_repl = false, close_window_on_exit = true, preferred = setmetatable({}, { __newindex = function(tbl, k, v) vim.deprecate("config.preferred", "config.repl_definition", "3.1", "iron.nvim") rawset(tbl, k, v) end }), repl_definition = setmetatable({}, { __index = function(tbl, key) local repl_definitions = require("iron.fts")[key] local repl_def for _, v in pairs(repl_definitions) do if vim.fn.executable(v.command[1]) == 1 then repl_def = v break end end if repl_def == nil then error("Failed to locate REPL executable, aborting") else rawset(tbl, key, repl_def) return repl_def end end }), repl_filetype = function(bufnr, ft) return "iron" end, should_map_plug = false, repl_open_cmd = view.split.botright(40), current_view = 1, views = { view.bottom(40) }, mark = { -- Arbitrary numbers save_pos = 20, send = 77, }, buflisted = false, ignore_blank_lines = true, } -- HACK for LDoc to correctly link @see annotations config = vim.deepcopy(values) config.values = values return config ================================================ FILE: lua/iron/core.lua ================================================ -- luacheck: globals vim unpack local log = require("iron.log") local ll = require("iron.lowlevel") local focus = require("iron.visibility").focus local config = require("iron.config") local marks = require("iron.marks") local is_windows = require("iron.util.os").is_windows local view = require("iron.view") local dap = require("iron.dap") local autocmds = {} --- Core functions of iron -- @module core local core = {} --- Local helpers for creating a new repl. -- Should be used by core functions, but not -- exposed to the end-user -- @local local new_repl = {} --- Create a new repl on the current window -- Simple wrapper around the low level functions -- Useful to avoid rewriting the get_def + create + save pattern -- @param ft filetype -- @param bufnr buffer to be used. -- @param current_bufnr current buffer. -- @tparam cleanup function Function to cleanup if call fails -- @return saved snapshot of repl metadata new_repl.create = function(ft, bufnr, current_bufnr, cleanup) local meta local success, repl = pcall(ll.get_repl_def, ft) if not success and cleanup ~= nil then cleanup() error(repl) end success, meta = pcall( ll.create_repl_on_current_window, ft, repl, bufnr, current_bufnr ) if success then ll.set(ft, meta) local filetype = config.repl_filetype(bufnr, ft) if filetype ~= nil then vim.api.nvim_set_option_value("filetype", filetype, { buf = bufnr }) end return meta elseif cleanup ~= nil then cleanup() end error(meta) end --- Create a new repl on a new repl window -- Adds a layer on top of @{new_repl.create}, -- ensuring it is created on a new window -- @param ft filetype -- @return saved snapshot of repl metadata new_repl.create_on_new_window = function(ft) local current_bufnr = vim.api.nvim_get_current_buf() local bufnr = ll.new_buffer() local replwin = ll.new_window(bufnr) vim.api.nvim_set_current_win(replwin) local meta = new_repl.create(ft, bufnr, current_bufnr, function() vim.api.nvim_win_close(replwin, true) vim.api.nvim_buf_delete(bufnr, { force = true }) end) return meta end --- Creates a repl in the current window -- @param ft the filetype of the repl to be created -- @treturn table metadata of the repl core.repl_here = function(ft) local meta = ll.get(ft) if ll.repl_exists(meta) then vim.api.nvim_set_current_buf(meta.bufnr) return meta else local current_bufnr = vim.api.nvim_get_current_buf() local bufnr = ll.new_buffer() return new_repl.create(ft, bufnr, current_bufnr, function() vim.api.nvim_buf_delete(bufnr, { force = true }) end) end end --- Restarts the repl for the current buffer -- First, check if the cursor is on top or a REPL -- Then, start a new REPL of the same type and enter it into the window -- Afterwards, wipe out the old REPL buffer -- This is done without asking for confirmation, so user beware -- @todo Split into "restart a repl" and "do X for current buffer's repl" -- @return saved snapshotof repl metadata core.repl_restart = function() local bufnr_here = vim.fn.bufnr("%") local ft = ll.get_repl_ft_for_bufnr(bufnr_here) local current_bufnr = vim.api.nvim_get_current_buf() if ft ~= nil then local bufnr = ll.new_buffer() local meta = new_repl.create( ft, bufnr, current_bufnr, function() vim.api.nvim_buf_delete(bufnr, { force = true }) end ) -- created a new one, now have to kill the old one vim.api.nvim_buf_delete(bufnr_here, { force = true }) return meta else ft = ll.get_buffer_ft(0) local meta = ll.get(ft) if ll.repl_exists(meta) then local replwin = vim.fn.bufwinid(meta.bufnr) local currwin = vim.api.nvim_get_current_win() local new_meta if replwin == nil or replwin == -1 then new_meta = new_repl.create_on_new_window(ft) else vim.api.nvim_set_current_win(replwin) local bufnr = ll.new_buffer() new_meta = new_repl.create( ft, bufnr, current_bufnr, function() vim.api.nvim_buf_delete(bufnr, {force = true}) end ) end vim.api.nvim_set_current_win(currwin) vim.api.nvim_buf_delete(meta.bufnr, { force = true }) return new_meta else error('No repl found in current buffer; cannot restart') end end end --- Sends a close request to the repl -- if @{config.values.close_window_on_exit} is set to true, -- all windows associated with that repl will be closed. -- Otherwise, this will only finish the process. -- @param ft filetype core.close_repl = function(ft) -- see the similar logic on core.send to see how the REPLs created by -- core.attach is handled. local meta = vim.b[0].repl if not meta or not ll.repl_exists(meta) then ft = ft or ll.get_buffer_ft(0) meta = ll.get(ft) end if not ll.repl_exists(meta) then return end ll.send_to_repl(meta, string.char(04)) end --- Hides the repl windows for a given filetype -- @param ft filetype core.hide_repl = function(ft) ft = ft or ll.get_buffer_ft(0) local meta = ll.get(ft) if ll.repl_exists(meta) then local window = vim.fn.bufwinid(meta.bufnr) if window ~= -1 then vim.api.nvim_win_hide(window) end end end --- Creates a repl for a given filetype -- It should open a new repl on a new window for the filetype -- supplied as argument. -- @param ft filetype core.repl_for = function(ft) if dap.is_dap_session_running() then -- If there's a dap session running, default to the dap repl. By -- intercepting here, we can support dap repls in filetypes that aren't -- normally supported (e.g. java). -- TODO find and open the dap repl window? return end local meta = ll.get(ft) if ll.repl_exists(meta) then local currwin = vim.api.nvim_get_current_win() config.visibility(meta.bufnr, function() local winid = ll.new_window(meta.bufnr) vim.api.nvim_win_set_buf(winid, meta.bufnr) vim.api.nvim_set_current_win(currwin) return winid end) return meta else local currwin = vim.api.nvim_get_current_win() meta = new_repl.create_on_new_window(ft) vim.api.nvim_set_current_win(currwin) return meta end end --- Moves to the repl for given filetype -- When it doesn't exist, a new repl is created -- directly moving the focus to it. -- @param ft filetype core.focus_on = function(ft) local meta = ll.get(ft) if ll.repl_exists(meta) then focus(meta.bufnr, function() local winid = ll.new_window(meta.bufnr) vim.api.nvim_win_set_buf(winid, meta.bufnr) return winid end) return meta else return new_repl.create_on_new_window(ft) end end --- Sends data to the repl -- This is a top-level wrapper over the low-level -- functions. It should send data to the repl, ensuring -- it exists. -- @param ft filetype (will be inferred if not supplied) -- @tparam string|table data data to be sent to the repl. local send = function(ft, data) -- the buffer local variable `repl` is created by core.attach function to -- track non-default REPls. local meta = vim.b[0].repl if dap.is_dap_session_running() then dap.send_to_dap(data) return end -- However, this attached meta may associated with a REPL that has been -- closed, we need to check for that. -- If the attached REPL is not existed or has been closed, we will try to -- get the REPL meta based on the ft of current buffer. if not meta or not ll.repl_exists(meta) then ft = ft or ll.get_buffer_ft(0) if data == nil then return end meta = ll.get(ft) end -- If the repl doesn't exist, it will be created if not ll.repl_exists(meta) then meta = core.repl_for(ft) end ll.send_to_repl(meta, data) end -- TODO fix this hack fix that allows ipython with Windows OS -- To fix this, there needs to be some sort of check on the progress of the -- call to vim.fn.chansend(meta.job, dt) inside of ll.send_to_repl. The defer -- is here to ensure that sending of string.char(13) sends after the commands -- are finished sending to the ipython REPL core.send = function(ft, data) if not is_windows() then send(ft, data) else send(ft, data) -- This is a hack fix that allows windows ipython to run the commands sent -- to the repl. However, the same issue will arise as before (the command sent -- to the repl but the code not running) if many lines are sent to the ipython -- repl (many as in more than 150 milliseconds worth). It appear that windows -- and powershell works fine though. vim.defer_fn(function() send(nil, string.char(13)) if type(data) ~= "string" then send(nil, string.char(13)) end end, 150) end end core.send_file = function(ft) core.send(ft, vim.api.nvim_buf_get_lines(0, 0, -1, false)) end --- Sends the line under the cursor to the repl -- Builds upon @{core.send}, extracting -- the data beforehand. core.send_line = function() local linenr = vim.api.nvim_win_get_cursor(0)[1] - 1 local cur_line = vim.api.nvim_buf_get_lines(0, linenr, linenr + 1, 0)[1] local width = vim.fn.strwidth(cur_line) if width == 0 then return end marks.set { from_line = linenr, from_col = 0, to_line = linenr, to_col = width - 1 } core.send(nil, cur_line) end --- Sends the buffer from the beginning until reching the line where the cursor is (inclusive) -- Builds upon @{core.send}, extracting -- the data beforehand. core.send_until_cursor = function() local linenr = vim.api.nvim_win_get_cursor(0)[1] - 1 local text_until_line = vim.api.nvim_buf_get_lines(0, 0, linenr + 1, 0) local last_line = vim.api.nvim_buf_get_lines(0, linenr, linenr + 1, 0)[1] local last_line_width = vim.fn.strwidth(last_line) marks.set { from_line = 0, from_col = 0, to_line = linenr, to_col = last_line_width - 1 } core.send(nil, text_until_line) end --- Marks visual selection and returns data for usage -- @treturn table Marked lines core.mark_visual = function() -- HACK Break out of visual mode vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('', false, true, true), 'nx', false) local b_line, b_col local e_line, e_col local mode = vim.fn.visualmode() b_line, b_col = unpack(vim.fn.getpos("'<"), 2, 3) e_line, e_col = unpack(vim.fn.getpos("'>"), 2, 3) if e_line < b_line or (e_line == b_line and e_col < b_col) then e_line, b_line = b_line, e_line e_col, b_col = b_col, e_col end local lines = vim.api.nvim_buf_get_lines(0, b_line - 1, e_line, 0) if #lines == 0 then return end if mode == "\22" then local b_offset = math.max(1, b_col) - 1 for ix, line in ipairs(lines) do -- On a block, remove all presiding chars unless b_col is 0/negative lines[ix] = vim.fn.strcharpart(line, b_offset, math.min(e_col, vim.fn.strwidth(line))) end elseif mode == "v" then local last = #lines local line_size = vim.fn.strwidth(lines[last]) local max_width = math.min(e_col, line_size) if (max_width < line_size) then -- If the selected width is smaller then total line, trim the excess lines[last] = vim.fn.strcharpart(lines[last], 0, max_width) end if b_col > 1 then -- on a normal visual selection, if the start column is not 1, trim the beginning part lines[1] = vim.fn.strcharpart(lines[1], b_col - 1) end end marks.set { from_line = b_line - 1, from_col = math.max(b_col - 1, 0), to_line = e_line - 1, to_col = math.min(e_col, vim.fn.strwidth(lines[#lines])) - 1 -- TODO Check whether this is actually true } if config.ignore_blank_lines then local b_lines = {} for _, line in ipairs(lines) do if line:gsub("^%s*(.-)%s*$", "%1") ~= '' then table.insert(b_lines, line) end end return b_lines else return lines end end --- Marks the supplied motion and returns the data for usage -- @tparam string mtype motion type -- @treturn table Marked lines core.mark_motion = function(mtype) local b_line, b_col local e_line, e_col b_line, b_col = unpack(vim.fn.getpos("'["), 2, 3) e_line, e_col = unpack(vim.fn.getpos("']"), 2, 3) local lines = vim.api.nvim_buf_get_lines(0, b_line - 1, e_line, 0) if #lines == 0 then return end if mtype == 'line' then b_col, e_col = 0, vim.fn.strwidth(lines[#lines]) end if e_col > 1 then lines[#lines] = vim.fn.strpart(lines[#lines], 0, e_col) end if b_col > 1 then lines[1] = vim.fn.strpart(lines[1], b_col - 1) end marks.set { from_line = b_line - 1, from_col = math.max(b_col - 1, 0), to_line = e_line - 1, to_col = e_col - 1 } marks.winrestview() return lines end --- Sends a chunk of text from a motion to the repl -- It is a simple wrapper over @{core.mark_motion} -- in which the data is extracted by that function and sent to the repl. -- @{core.send} will handle the null cases. -- Additionally, it restores the cursor position as a side-effect. -- @param mtype motion type core.send_motion = function(mtype) core.send(nil, core.mark_motion(mtype)) end --- Sends a chunk of text from a visual selection to the repl -- this is a simple wrapper over @{core.mark_visual} where -- the data is forwarded to the repl through @{core.send}, -- which will handle the null cases. core.visual_send = function() core.send(nil, core.mark_visual()) end --- Sends the paragraph to the REPL that the cursor is on core.send_paragraph = function() vim.cmd('normal! vip') vim.defer_fn(function() core.visual_send() end, 100) end --- Re-sends latest chunk of text. -- Sends text contained within a block delimited by -- the last sent chunk. Uses @{marks.get} to retrieve -- the boundaries. core.send_mark = function() local pos = marks.get() if pos == nil then return end local lines = vim.api.nvim_buf_get_lines(0, pos.from_line, pos.to_line + 1, 0) if #lines == 1 then if pos.from_col >= 1 or pos.to_col < vim.fn.strwidth(lines[1]) - 1 then lines[1] = vim.fn.strpart(lines[1], pos.from_col, pos.to_col - pos.from_col + 1) end else if pos.from_col >= 1 then lines[1] = vim.fn.strpart(lines[1], pos.from_col) end if pos.to_col < vim.fn.strwidth(lines[#lines]) - 1 then lines[#lines] = vim.fn.strpart(lines[#lines], 0, pos.to_col + 1) end end core.send(nil, lines) end --- Checks if line starts with a divider. -- Helper function for core.send_code_block. local line_starts_with_block_divider = function(line, block_dividers) for _, block_divider in pairs(block_dividers) do local length_block_divider = string.len(block_divider) if string.sub(line, 1, length_block_divider) == block_divider then return true end end end --- Sends lines between two block dividers. -- Block dividers can be defined as table in the -- repl_definition under key block_dividers. -- The buffer is scanned from cursor till first line -- starting with a block_divider or the start of buffer. -- The buffer is scanned till end of buffer for next line -- starting with a block divider or end of buffer. -- If no block_divider is defined an error is returned. -- If move is true, the cursor is moved to the next block_divider -- for jumping through the code. If move is false, the cursor is -- not moved. core.send_code_block = function(move) local block_dividers = config.repl_definition[vim.bo[0].filetype].block_dividers if block_dividers == nil then error("No block_dividers defined for this repl in repl_definition!") end local linenr = vim.api.nvim_win_get_cursor(0)[1] - 1 local buffer_text = vim.api.nvim_buf_get_lines(0, 0, -1, false) local mark_start = linenr while mark_start ~= 0 do local line_text = buffer_text[mark_start + 1] if line_starts_with_block_divider(line_text, block_dividers) then break end mark_start = mark_start - 1 end local buffer_length = vim.api.nvim_buf_line_count(0) local mark_end = linenr + 1 while mark_end < buffer_length do local line_text = buffer_text[mark_end + 1] if line_starts_with_block_divider(line_text, block_dividers) then break end mark_end = mark_end + 1 end mark_end = mark_end - 1 local col_end = string.len(buffer_text[mark_end + 1]) - 1 marks.set { from_line = mark_start, from_col = 0, to_line = mark_end, to_col = col_end, } core.send_mark() if move then vim.api.nvim_win_set_cursor(0, { math.min(mark_end + 2, buffer_length), 0 }) end end --- Attaches a buffer to a repl regardless of it's filetype -- If the repl doesn't exist it will be created core.attach = function(ft, target) local meta = ll.get(ft) if not ll.repl_exists(meta) then meta = core.repl_for(ft) end vim.b[target].repl = meta end --- Provide filtered list of supported fts -- Auxiliary function to be used by commands to show the user which fts they have -- available to start repls with -- @param partial input string -- @return table with supported filetypes matching input string local complete_fts = function(partial) local starts_with_partial = function(key) return key:sub(1, #partial) == partial end local custom_fts = vim.tbl_filter(starts_with_partial, vim.tbl_keys(config.repl_definition)) vim.list_extend(custom_fts, vim.tbl_filter( function(i) return (not vim.tbl_contains(custom_fts, i)) and starts_with_partial(i) end, vim.tbl_keys(require("iron.fts"))) ) return custom_fts end local get_ft = function(arg) if arg and arg ~= "" then return arg end return ll.get_buffer_ft(0) end --- List of commands created by iron.nvim -- They'll only be set up after calling the @{core.setup} function -- which makes it possible to delay initialization and make startup faster. -- @local -- @table commands -- @field IronRepl command for @{core.repl_for} local commands = { { "IronAttach", function(opts) core.attach(get_ft(opts.fargs[1]), vim.api.nvim_get_current_buf()) end, { nargs = "?", complete = complete_fts } }, { "IronRepl", function(opts) core.repl_for(get_ft(opts.fargs[1])) end, { nargs = "?", complete = complete_fts } }, { "IronHide", function(opts) core.hide_repl(get_ft(opts.fargs[1])) end, { nargs = "?", complete = complete_fts } }, { "IronSend", function(opts) local ft if opts.bang then ft = opts.fargs[1] opts.fargs[1] = "" else ft = ll.get_buffer_ft(0) end if ft == nil then return end local data = table.concat(opts.fargs, " ") core.send(ft, data) end, { bang = true, nargs = "+", complete = function(arg_lead, cmd_line) local cmd = vim.split(cmd_line, " ") if #cmd <= 2 and string.find(cmd[1], "!") then return complete_fts(arg_lead) end end } }, { "IronFocus", function(opts) local ft = get_ft(opts.fargs[1]) if ft == nil then return end core.focus_on(ft) end, { nargs = "?", complete = complete_fts } }, { "IronWatch", function(opts) local handler if opts.fargs[1] == "mark" then handler = core.send_mark elseif opts.fargs[1] == "file" then -- Wrap send_file so we ignore autocmd argument handler = function() core.send_file() end else error("Not a valid handler type") end core.watch(handler) end, { nargs = 1, complete = function(arg_lead, _) local starts_with_partial = function(key) return key:sub(1, #arg_lead) == arg_lead end return vim.tbl_filter(starts_with_partial, { "mark", "file" }) end } }, { "IronReplHere", function(opts) local ft = get_ft(opts.fargs[1]) if ft == nil then return end core.repl_here(ft) end, { nargs = "?", complete = complete_fts } }, { "IronRestart", function(_) core.repl_restart() end, { nargs = 0 } } } --- Wrapper for calling functions through motion. -- This should take care of the vim side of calling opfuncs. -- @param motion_fn_name name of the function in @{core} to be mapped core.run_motion = function(motion_fn_name) marks.winsaveview() vim.o.operatorfunc = "v:lua.require'iron.core'." .. motion_fn_name vim.api.nvim_feedkeys("g@", "ni", false) end core.unwatch = function(bufnr) local fname = vim.api.nvim_buf_get_name(bufnr) if autocmds[fname] ~= nil then vim.api.nvim_del_autocmd(autocmds[fname]) autocmds[fname] = nil end end core.watch = function(handler, bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() core.unwatch(bufnr) local fname = vim.api.nvim_buf_get_name(bufnr) autocmds[fname] = vim.api.nvim_create_autocmd("BufWritePost", { group = "iron", pattern = fname, callback = handler, desc = "Watch writes to buffer to send data to repl" }) end --- List of keymaps -- if @{config}.should\_map\_plug is set to true, -- then they will also be mapped to `` keymaps. -- @table named_maps -- @field send_motion mapping to send a motion/chunk to the repl -- @field send_mark Sends chunk within marked boundaries -- @field send_line sends current line to repl -- @field send_until_cursor sends the buffer from the start until the line where the cursor is (inclusive) to the repl -- @field visual_send sends visual selection to repl -- @field clear_hl clears highlighted chunk -- @field cr sends a to the repl -- @field interrupt sends a to the repl -- @field exit closes the repl -- @field clear clears the text buffer of the repl local named_maps = { -- basic interaction with the repl send_motion = { { 'n' }, function() require("iron.core").run_motion("send_motion") end }, send_mark = { { 'n' }, core.send_mark }, send_line = { { 'n' }, core.send_line }, send_until_cursor = { { 'n' }, core.send_until_cursor }, send_file = { { 'n' }, core.send_file }, visual_send = { { 'v' }, core.visual_send }, send_paragraph = { { 'n' }, core.send_paragraph }, send_code_block = { { 'n' }, function() core.send_code_block(false) end }, send_code_block_and_move = { { 'n' }, function() core.send_code_block(true) end }, -- REPL restart_repl = { { 'n' }, function() vim.cmd("IronRestart") end }, toggle_repl = { { 'n' }, function() vim.cmd("IronRepl") end }, -- Marks mark_motion = { { 'n' }, function() require("iron.core").run_motion("mark_motion") end }, mark_visual = { { 'v' }, core.mark_visual }, remove_mark = { { 'n' }, marks.drop_last }, -- Force clear highlight clear_hl = { { 'v' }, marks.clear_hl }, -- Sending special characters to the repl cr = { { 'n' }, function() core.send(nil, string.char(13)) end }, interrupt = { { 'n' }, function() core.send(nil, string.char(03)) end }, exit = { { 'n' }, core.close_repl }, clear = { { 'n' }, function() core.send(nil, string.char(12)) end }, } local tmp_migration = { repeat_cmd = "send_mark" } local snake_to_kebab = function(name) return name:gsub("_", "-") end --- Sets up the configuration for iron to run. -- Also, defines commands and keybindings for iron to run. -- @param opts table of the configuration to be applied -- @tparam table opts.config set of config values to override default on @{config} -- @tparam table opts.keymaps set of keymaps to apply, based on @{named_maps} core.setup = function(opts) config.namespace = vim.api.nvim_create_namespace("iron") vim.api.nvim_create_augroup("iron", {}) if opts.config then if type(opts.config.repl_open_cmd) ~= "table" then ll.tmp.repl_open_cmd = opts.config.repl_open_cmd else for idx, cmd in ipairs(opts.config.repl_open_cmd) do if idx == 1 then ll.tmp.repl_open_cmd = cmd end named_maps["toggle_repl_with_cmd_" .. idx] = { { 'n' }, function() ll.tmp.repl_open_cmd = cmd vim.cmd('IronRepl') end } end end if opts.config.dap_integration then dap.enable_integration() end if ll.tmp.repl_open_cmd == nil then local msg = "A default repl_open_cmd was not set. " msg = msg .. "Please set a default by adding '_DEFAULT' " msg = msg .. "to the end of one of your repl_open_cmd names." error(msg) end for k, v in pairs(opts.config) do config[k] = v end else ll.tmp.repl_open_cmd = config.repl_open_cmd end if config.highlight_last ~= false then local hl_cfg = opts.highlight or { bold = true } vim.api.nvim_set_hl(0, config.highlight_last, hl_cfg) end for _, command in ipairs(commands) do vim.api.nvim_create_user_command(unpack(command)) end if config.should_map_plug then vim.deprecate("config.should_map_plug", "core.setup{keymaps = {...}}", "3.1", "iron.nvim") for key, keymap in pairs(named_maps) do local mapping = vim.deepcopy(keymap) table.insert(mapping, 2, "(iron-" .. snake_to_kebab(key) .. ")") table.insert(mapping, { silent = true }) vim.keymap.set(unpack(mapping)) end end if opts.keymaps ~= nil then for key, lhs in pairs(opts.keymaps) do if tmp_migration[key] ~= nil then log.deprecate( "core.setup{keymaps." .. key .. "}", "core.setup{keymaps." .. tmp_migration[key] .. "}", "3.1", "iron.nvim" ) key = tmp_migration[key] end if named_maps[key] == nil then error("Key `" .. key .. "` doesn't exist, therefore there's nothing to be applied") else local mapping = vim.deepcopy(named_maps[key]) table.insert(mapping, 2, lhs) table.insert(mapping, { silent = true, desc = 'iron_repl_' .. key }) vim.keymap.set(unpack(mapping)) end end end end return core ================================================ FILE: lua/iron/dap.lua ================================================ local M = {} local is_dap_integration_enabled = false --- Sets up a hook to keep track of the DAP session state. function M.enable_integration() is_dap_integration_enabled = true end --- Returns true if dap_integration is enabled and a dap session is running. --- This function will always return false if dap_integration is not enabled. --- @return boolean function M.is_dap_session_running() local has_dap, dap = pcall(require, 'dap') return has_dap and is_dap_integration_enabled and dap.session() ~= nil end --- Send the lines to the dap-repl --- @param lines string|string[] function M.send_to_dap(lines) local text if type(lines) == 'table' then text = table.concat(lines, "\n"):gsub('\r', '') else text = lines end require('dap').repl.execute(text) require('dap').repl.open() end return M ================================================ FILE: lua/iron/debug_level.lua ================================================ --- Sets the level of debug --@module debug_level local debug_level = {} debug_level.fatal = 1 debug_level.err = 2 debug_level.warn = 3 debug_level.info = 4 return debug_level ================================================ FILE: lua/iron/fts/clojure.lua ================================================ local clojure = {} clojure.boot = { command = {"boot", "repl"}, } clojure.lein = { command = {"lein", "repl"}, } clojure.clj = { command = {"clj"}, } return clojure ================================================ FILE: lua/iron/fts/common.lua ================================================ local is_windows = require("iron.util.os").is_windows local extend = require("iron.util.tables").extend local open_code = "\27[200~" local close_code = "\27[201~" local cr = "\13" local common = {} ---@param table table table of strings ---@param substring string --- Checks in any sting in the table contains the substring local contains = function(table, substring) for _, v in ipairs(table) do if string.find(v, substring) then return true end end return false end ---@param lines table -- Removes empty lines. On unix this includes lines only with whitespaces. local function remove_empty_lines(lines) local newlines = {} for _, line in pairs(lines) do if string.len(line:gsub("[ \t]", "")) > 0 then table.insert(newlines, line) end end return newlines end ---@param s string --- A helper function using in bracked_paste_python. -- Checks in a string starts with any of the exceptions. local function python_close_indent_exceptions(s) local exceptions = { "elif", "else", "except", "finally", "#" } for _, exception in ipairs(exceptions) do local pattern0 = "^" .. exception .. "[%s:]" local pattern1 = "^" .. exception .. "$" if string.match(s, pattern0) or string.match(s, pattern1) then return true end end return false end common.format = function(repldef, lines) assert(type(lines) == "table", "Supplied lines is not a table") local new -- passing the command is for python. this will not affect bracketed_paste. if repldef.format then return repldef.format(lines, { command = repldef.command }) elseif #lines == 1 then new = lines else new = extend(repldef.open, lines, repldef.close) end if #new > 0 then if not is_windows() then new[#new] = new[#new] .. cr end end return new end common.bracketed_paste = function(lines) if #lines == 1 then return { lines[1] .. cr } else local new = { open_code .. lines[1] } for line = 2, #lines do table.insert(new, lines[line]) end table.insert(new, close_code .. cr) return new end end --- @param lines table "each item of the table is a new line to send to the repl" --- @return table "returns the table of lines to be sent the the repl with -- the return carriage added" common.bracketed_paste_python = function(lines, extras) local result = {} local cmd = extras["command"] local pseudo_meta = { current_buffer = vim.api.nvim_get_current_buf()} if type(cmd) == "function" then cmd = cmd(pseudo_meta) end local windows = is_windows() local python = false local ipython = false local ptpython = false if contains(cmd, "ipython") then ipython = true elseif contains(cmd, "ptpython") then ptpython = true else python = true end lines = remove_empty_lines(lines) local indent_open = false for i, line in ipairs(lines) do if string.match(line, "^%s") ~= nil then indent_open = true end table.insert(result, line) if windows and python or not windows then if i < #lines and indent_open and string.match(lines[i + 1], "^%s") == nil then if not python_close_indent_exceptions(lines[i + 1]) then indent_open = false table.insert(result, cr) end end end end local newline = windows and "\r\n" or cr if #result == 0 then -- handle sending blank lines table.insert(result, cr) elseif #result > 0 and result[#result]:sub(1, 1) == " " then -- Since the last line of code is indented, the Python REPL -- requires and extra newline in order to execute the code table.insert(result, newline) else if not is_windows() then table.insert(result, "") end end if ptpython then table.insert(result, 1, open_code) table.insert(result, close_code) table.insert(result, "\n") end return result end return common ================================================ FILE: lua/iron/fts/cpp.lua ================================================ local cpp = {} local function magic(str) -- remove every space at beginning of a line str = str:gsub('^%s+', '') -- remove all comments str = str:gsub('//(.*)', '') -- remove every space at end of a line str = str:gsub('%s+$', '') -- remove line break and extra spaces eventually str = str:gsub([[\$]], '') str = str:gsub('%s+$', '') return str end local function lastmagic(str) -- check what character is the last of the line if str:len() == 0 then return false end local lastchar = str:sub(-1, -1) return lastchar == ';' or lastchar == '{' or lastchar == '}' end local function format(lines) if #lines == 1 then return {magic(lines[1]) .. '\13'} else local new = {} local aus = '' for line = 1, #lines do -- concatenate lines if they do not end with a lastmagic character. local l = magic(lines[line]) aus = aus .. l if lastmagic(l) then table.insert(new, aus) aus = '' end end return table.insert(new, '') end end cpp.root = {command = {'root', '-l'}, format = format} return cpp ================================================ FILE: lua/iron/fts/csh.lua ================================================ local csh = {} csh.csh = { command = {"csh"}, } csh.tcsh = { command = {"tch"}, } return csh ================================================ FILE: lua/iron/fts/elixir.lua ================================================ local elixir = {} elixir.iex = { command = {"iex"}, } return elixir ================================================ FILE: lua/iron/fts/elm.lua ================================================ local elm = {} elm.elm = { command = {"elm", "repl"}, } elm.elm_legacy = { command = {"elm-repl"}, } return elm ================================================ FILE: lua/iron/fts/erlang.lua ================================================ local erlang = {} erlang.erl = { command = {"erl"}, } return erlang ================================================ FILE: lua/iron/fts/fennel.lua ================================================ local fennel = {} fennel.fennel = { command = {"fennel"}, } return fennel ================================================ FILE: lua/iron/fts/fish.lua ================================================ local fish = {} fish.fish = { command = {"fish"}, } return fish ================================================ FILE: lua/iron/fts/forth.lua ================================================ local forth = {} forth.gforth = { command = {"gforth"} } return forth ================================================ FILE: lua/iron/fts/haskell.lua ================================================ local haskell = {} haskell.stack_intero = { command = {"stack", "ghci", "--with-ghc", "intero"}, } haskell.stack = { command = {"stack", "ghci"}, } haskell.cabal = { command = {"cabal", "repl"}, } haskell.ghci = { command = {"ghci"}, } return haskell ================================================ FILE: lua/iron/fts/hy.lua ================================================ local hy = {} hy.hy = { command = {"hy"} } return hy ================================================ FILE: lua/iron/fts/init.lua ================================================ -- luacheck: globals unpack local fts = { clojure = require("iron.fts.clojure"), cpp = require("iron.fts.cpp"), csh = require("iron.fts.csh"), elixir = require("iron.fts.elixir"), elm = require("iron.fts.elm"), erlang = require("iron.fts.erlang"), fennel = require("iron.fts.fennel"), forth = require("iron.fts.forth"), haskell = require("iron.fts.haskell"), hy = require("iron.fts.hy"), janet = require("iron.fts.janet"), javascript = require("iron.fts.javascript"), julia = require("iron.fts.julia"), lisp = require("iron.fts.lisp"), lua = require("iron.fts.lua"), mma = require("iron.fts.mma"), ocaml = require("iron.fts.ocaml"), php = require("iron.fts.php"), prolog = require("iron.fts.prolog"), ps1 = require("iron.fts.ps1"), pure = require("iron.fts.pure"), python = require("iron.fts.python"), r = require("iron.fts.r"), racket = require("iron.fts.racket"), ruby = require("iron.fts.ruby"), sbt_scala = require("iron.fts.sbt"), scala = require("iron.fts.scala"), scheme = require("iron.fts.scheme"), sh = require("iron.fts.sh"), stata = require("iron.fts.stata"), tcl = require("iron.fts.tcl"), typescript = require("iron.fts.typescript"), zsh = require("iron.fts.zsh"), fish = require("iron.fts.fish") } return fts ================================================ FILE: lua/iron/fts/janet.lua ================================================ local janet = {} janet.janet = { command = {"janet"}, } return janet ================================================ FILE: lua/iron/fts/javascript.lua ================================================ local javascript = {} javascript.node = { command = {"node"}, open = ".editor\n", close = "\04", } return javascript ================================================ FILE: lua/iron/fts/julia.lua ================================================ local julia = {} julia.julia = { command = {"julia"}, } return julia ================================================ FILE: lua/iron/fts/lisp.lua ================================================ local lisp = {} lisp.sbcl = { command = {"sbcl"}, } lisp.clisp = { command = {"clisp"}, } return lisp ================================================ FILE: lua/iron/fts/lua.lua ================================================ local lua = {} lua.lua = { command = {"lua"}, } return lua ================================================ FILE: lua/iron/fts/markdown.lua ================================================ local markdown = {} markdown.aichat = { command = "aichat", format = require("iron.fts.common").bracketed_paste, } return markdown ================================================ FILE: lua/iron/fts/mma.lua ================================================ local mma = {} mma.math = { command = { "math" }, } return mma ================================================ FILE: lua/iron/fts/ocaml.lua ================================================ local ocaml = {} ocaml.utop = { command = {"utop"}, } ocaml.ocamltop = { command = {"ocamltop"}, } return ocaml ================================================ FILE: lua/iron/fts/php.lua ================================================ local php = {} php.psysh = { command = {"psysh"}, } php.php = { command = {"php", "-a"}, } return php ================================================ FILE: lua/iron/fts/prolog.lua ================================================ local prolog = {} prolog.gprolog = { command = {"gprolog"}, } prolog.swipl = { command = {"swipl"}, } return prolog ================================================ FILE: lua/iron/fts/ps1.lua ================================================ local ps1 = {} ps1.ps1 = { command = {"powershell", "-noexit", "-executionpolicy", "bypass"}, } return ps1 ================================================ FILE: lua/iron/fts/pure.lua ================================================ local pure = {} pure.pure = { command = {"pure"}, open = ":paste\n", close = "\04", } return pure ================================================ FILE: lua/iron/fts/python.lua ================================================ -- luacheck: globals vim local bracketed_paste_python = require("iron.fts.common").bracketed_paste_python local python = {} local executable = function(exe) return vim.api.nvim_call_function('executable', {exe}) == 1 end local pyversion = executable('python3') and 'python3' or 'python' local def = function(cmd) return { command = cmd, format = bracketed_paste_python } end python.ptipython = def({"ptipython"}) python.ipython = def({"ipython", "--no-autoindent"}) python.ptpython = def({"ptpython"}) python.python = def({pyversion}) return python ================================================ FILE: lua/iron/fts/r.lua ================================================ local bracketed_paste = require("iron.fts.common").bracketed_paste local r = {} r.R = { command = { "R" }, format = bracketed_paste } r.radian = { command = { "radian" }, format = bracketed_paste } return r ================================================ FILE: lua/iron/fts/racket.lua ================================================ local racket = {} racket.racket = { command = {"racket"}, } return racket ================================================ FILE: lua/iron/fts/ruby.lua ================================================ local ruby = {} ruby.irb = { command = {"irb"}, } return ruby ================================================ FILE: lua/iron/fts/sbt.lua ================================================ local sbt = {} sbt.sbt = { command = {"sbt"}, open = ":paste\n", close = "\04", } return sbt ================================================ FILE: lua/iron/fts/scala.lua ================================================ local scala = {} local def = function(command) return { command = command, open = ":paste", close = "\04" } end scala.sbt = def{"sbt"} scala.scala = def{"scala"} return scala ================================================ FILE: lua/iron/fts/scheme.lua ================================================ local scheme = {} scheme.guile = { command = {"guile"}, } scheme.csi = { command = {"csi"}, } return scheme ================================================ FILE: lua/iron/fts/sh.lua ================================================ local sh = {} sh.bash = { command = {"bash"}, } sh.sh = { command = function(meta) local bufnr = meta.current_bufnr if vim.b[bufnr].is_posix == 1 then return {"sh"} elseif vim.b[bufnr].is_bash == 1 then return {"bash"} elseif vim.b[bufnr].is_kornshell == 1 then return {"ksh"} else return {"sh"} end end, } return sh ================================================ FILE: lua/iron/fts/stata.lua ================================================ local stata = {} stata.stata = { command = { "stata", "-q" }, } return stata ================================================ FILE: lua/iron/fts/tcl.lua ================================================ local tcl = {} tcl.tclsh = { command = {"tclsh"}, } return tcl ================================================ FILE: lua/iron/fts/typescript.lua ================================================ local typescript = {} typescript.ts = { command = {"ts-node"}, open = ".editor\n", close = "\04", } return typescript ================================================ FILE: lua/iron/fts/zsh.lua ================================================ local zsh = {} zsh.zsh = { command = {"zsh"}, } return zsh ================================================ FILE: lua/iron/init.lua ================================================ -- luacheck: globals unpack vim local iron = { -- Will eventually be removed config = require("iron.config"), -- Most likely shouldn't be exposed here ll = require("iron.lowlevel"), core = require("iron.core"), setup = require("iron.core").setup } return iron ================================================ FILE: lua/iron/log.lua ================================================ -- luacheck: globals vim local log = {} if vim.deprecate then log.deprecate = vim.deprecate else log.deprecate = function(old, new, version, app) local message = [[%s is deprecated, use %s instead. See :h deprecated This function will be removed in %s version %s]] error(string.format(message, old, new, app, version)) end end return log ================================================ FILE: lua/iron/lowlevel.lua ================================================ -- luacheck: globals vim -- TODO Remove config from this layer local config = require("iron.config") local fts = require("iron.fts") local providers = require("iron.providers") local format = require("iron.fts.common").format local view = require("iron.view") local is_windows = require("iron.util.os").is_windows --- Low level functions for iron -- This is needed to reduce the complexity of the user API functions. -- There are a few rules to the functions in this document: -- * They should not interact with each other -- * An exception for this is @{lowlevel.get_repl_def} during the transition to v3 -- * They should do one small thing only -- * They should not care about setting/cleaning up state (i.e. moving back to another window) -- * They must be explicit in their documentation about the state changes they cause. -- @module lowlevel -- @alias ll local ll = {} ll.store = {} -- Quick fix for changing repl_open_cmd ll.tmp = {} -- TODO This should not be part of lowlevel ll.get = function(ft) if ft == nil or ft == "" then error("Empty filetype") end return config.scope.get(ll.store, ft) end -- TODO this should not be part of lowlevel ll.set = function(ft, fn) return config.scope.set(ll.store, ft, fn) end ll.get_buffer_ft = function(bufnr) local ft = vim.bo[bufnr].filetype if ft == nil or ft == "" then error("Empty filetype") elseif fts[ft] == nil and config.repl_definition[ft] == nil then error("There's no REPL definition for current filetype "..ft) end return ft end --- Creates the repl in the current window -- This function effectively creates the repl without caring -- about window management. It is expected that the client -- ensures the right window is created and active before calling this function. -- If @{\\config.close_window_on_exit} is set to true, it will plug a callback -- to the repl so the window will automatically close when the process finishes -- @param ft filetype of the current repl -- @param repl definition of the repl being created -- @param repl.command table with the command to be invoked. -- @param bufnr Buffer to be used -- @param current_bufnr Current buffer -- @param opts Options passed through to the terminal -- @warning changes current window's buffer to bufnr -- @return unsaved metadata about created repl ll.create_repl_on_current_window = function(ft, repl, bufnr, current_bufnr, opts) vim.api.nvim_win_set_buf(0, bufnr) -- TODO Move this out of this function -- Checking config should be done on an upper layer. -- This layer should be simpler opts = opts or {} if config.close_window_on_exit then opts.on_exit = function() local bufwinid = vim.fn.bufwinid(bufnr) while bufwinid ~= -1 do vim.api.nvim_win_close(bufwinid, true) bufwinid = vim.fn.bufwinid(bufnr) end if vim.api.nvim_buf_is_valid(bufnr) then vim.api.nvim_buf_delete(bufnr, { force = true }) end end else opts.on_exit = function() end end local cmd = repl.command if type(repl.command) == 'function' then local meta = { current_bufnr = current_bufnr, } cmd = repl.command(meta) end if repl.env then opts.env = repl.env end local job_id = vim.fn.termopen(cmd, opts) return { ft = ft, bufnr = bufnr, job = job_id, repldef = repl } end --- Wrapper function for getting repl definition from config -- This allows for an easier transition between old and new methods -- @tparam string ft filetype of the desired repl -- @return repl definition ll.get_repl_def = function(ft) -- TODO should not call providers directly, but from config return config.repl_definition[ft] or providers.first_matching_binary(ft) end --- Creates a new window for placing a repl. -- Expected to be called before creating the repl. -- It knows nothing about the repl and only takes in account the -- configuration. -- @warning might change the current window -- @param bufnr buffer to be used -- @param repl_open_cmd command to be used to open the repl. if nil than will use config.repl_open_cmd -- @return window id of the newly created window ll.new_window = function(bufnr, repl_open_cmd) if repl_open_cmd == nil then repl_open_cmd = ll.tmp.repl_open_cmd end if type(repl_open_cmd) == "function" then local result = repl_open_cmd(bufnr) if type(result) == "table" then return view.openfloat(result, bufnr) else return result end else vim.cmd(repl_open_cmd) vim.api.nvim_set_current_buf(bufnr) return vim.fn.bufwinid(bufnr) end end --- Creates a new buffer to be used by the repl -- @return the buffer id ll.new_buffer = function() return vim.api.nvim_create_buf(config.buflisted, config.scratch_repl) end --- Wraps the condition checking of whether a repl exists -- created for convenience -- @tparam table meta metadata for repl. Can be nil. -- @treturn boolean whether the repl exists ll.repl_exists = function(meta) return meta ~= nil and vim.api.nvim_buf_is_loaded(meta.bufnr) end --- Sends data to an existing repl of given filetype -- The content supplied is ensured to be a table of lines, -- being coerced if supplied as a string. -- As a side-effect of pasting the contents to the repl, -- it changes the scroll position of that window. -- Does not affect currently active window and its cursor position. -- @tparam table meta metadata for repl. Should not be nil -- @tparam string ft name of the filetype -- @tparam string|table data A multiline string or a table containing lines to be sent to the repl -- @warning changes cursor position if window is visible ll.send_to_repl = function(meta, data) local dt = data if data == string.char(12) then vim.fn.chansend(meta.job, {string.char(12)}) return end if type(data) == "string" then dt = vim.split(data, '\n') end dt = format(meta.repldef, dt) local window = vim.fn.bufwinid(meta.bufnr) if window ~= -1 then vim.api.nvim_win_set_cursor(window, {vim.api.nvim_buf_line_count(meta.bufnr), 0}) end --TODO check vim.api.nvim_chan_send --TODO tool to get the progress of the chan send function if is_windows() then vim.fn.chansend(meta.job, table.concat(dt, "\r")) else vim.fn.chansend(meta.job, dt) end if window ~= -1 then vim.api.nvim_win_set_cursor(window, {vim.api.nvim_buf_line_count(meta.bufnr), 0}) end end --- Reshapes the repl window according to a preset config described in views -- @tparam table meta metadata for the repl -- @tparam string|number key either name or index in the table for the preset to be active ll.set_window_shape = function(meta, key) local window = vim.fn.bufwinid(meta.bufnr) local preset = config.views[key] if preset ~= nil then if type(preset) == "function" then preset = preset(meta.bufnr) end vim.api.nvim_win_set_config(window, preset) end end --- Closes the window -- @tparam table meta metadata for the repl ll.close_window = function(meta) local window = vim.fn.bufwinid(meta.bufnr) vim.api.nvim_win_close(window, true) end --- Tries to look up the corresponding filetype of a REPL -- If the corresponding buffer number is a repl, -- return its filetype otherwise return nil -- @tparam int bufnr number of the buffer being checked -- @treturn string filetype of the buffer's repl (or nil if it doesn't have a repl associated) ll.get_repl_ft_for_bufnr = function(bufnr) for _, values in pairs(ll.store) do for _, meta in pairs(values) do if meta.bufnr == bufnr then return meta.ft end end end end return ll ================================================ FILE: lua/iron/marks.lua ================================================ -- luacheck: globals vim local config = require("iron.config") --- Marks management for iron -- This is an intermediary layer between iron and neovim's -- extmarks. Used primarily to manage the text sent to the repl, -- but can also be used to set highlight and possibly virtualtext. local marks = {} --- Sets a mark for given options -- The mark is set for a preconfigured position id, which will be used by -- @{marks.get} for retrieval. -- @tparam table opts table with the directives -- @tparam number opts.from_line line where the mark starts -- @tparam number opts.to_line line where the mark ends. Can be ignored on single line marks -- @tparam number opts.from_col column where the mark starts -- @tparam number opts.to_col column where the mark ends -- @tparam string|false opts.hl Highlight group to be used or false if no highlight should be done. marks.set = function(opts) local extmark_config = { id = config.mark.send, -- to_line can be ignored if it's single line end_line = opts.to_line or opts.from_line, end_col = opts.to_col + 1 } if opts.hl ~= nil then -- Intentionally check here -- so opts.false disables highlight if opts.hl ~= false then extmark_config.hl_group = opts.hl end elseif config.highlight_last then extmark_config.hl_group = config.highlight_last end vim.api.nvim_buf_set_extmark(0, config.namespace, opts.from_line, opts.from_col, extmark_config) end marks.drop_last = function() vim.api.nvim_buf_del_extmark(0, config.namespace, config.mark.send) end marks.clear_hl = function() local payload = marks.get() payload.hl = false marks.set(payload) end --- Retrieves the mark with a position id. marks.get = function() local mark_pos = vim.api.nvim_buf_get_extmark_by_id(0, config.namespace, config.mark.send, {details = true}) if #mark_pos == 0 then return nil end return { from_line = mark_pos[1], from_col = mark_pos[2], to_line = mark_pos[3].end_row, to_col = mark_pos[3].end_col - 1 } end marks.winrestview = function() local mark = vim.api.nvim_buf_get_extmark_by_id(0, config.namespace, config.mark.save_pos, {}) if #mark ~= 0 then -- winrestview is 1-based vim.fn.winrestview({lnum = mark[1] + 1, col = mark[2]}) vim.api.nvim_buf_del_extmark(0, config.namespace, config.mark.save_pos) end end marks.winsaveview = function() local pos = vim.fn.winsaveview() vim.api.nvim_buf_set_extmark(0, config.namespace, pos.lnum - 1, pos.col, {id = config.mark.save_pos}) end return marks ================================================ FILE: lua/iron/providers.lua ================================================ -- luacheck: globals vim local fts = require("iron.fts") local providers = {} --[[ TODO ensure there's a default provider The user should configure default provider; if none provided, use `first_matching_binary`. The user should also be allowed to set specific providers for specific languages --]] providers.first_matching_binary = function(ft) local repl_definitions = fts[ft] if not repl_definitions then error("No repl definition configured for " .. ft) end local repl_def for _, v in pairs(repl_definitions) do if vim.fn.executable(v.command[1]) == 1 then repl_def = v break end end if not repl_def then error("Couldn't find a binary available for " .. ft) end return repl_def end return providers ================================================ FILE: lua/iron/scope.lua ================================================ -- luacheck: globals vim local scope = {} local ensure_key = function(map, key) map[key] = map[key] or {} end local default_map_set = function(map, base, key, value) ensure_key(map, base) map[base][key] = value end scope.tab_based = { set = function(memory, ft, repl_data) local tab = vim.fn.tabpagenr() default_map_set(memory, ft, "tab_" .. tab, repl_data) return repl_data end, get = function(memory, ft) ensure_key(memory, ft) local tab = vim.fn.tabpagenr() return memory[ft]["tab_" .. tab] end } scope.path_based = { set = function(memory, ft, repl_data) local pwd = vim.fn.getcwd() default_map_set(memory, ft, "pwd_" .. pwd, repl_data) return repl_data end, get = function(memory, ft) ensure_key(memory, ft) local pwd = vim.fn.getcwd() return memory[ft]["pwd_" .. pwd] end } scope.singleton = { set = function(memory, ft, repl_data) ensure_key(memory, ft) default_map_set(memory, ft, "singleton", repl_data) return repl_data end, get = function(memory, ft) ensure_key(memory, ft) return memory[ft]["singleton"] end } return scope ================================================ FILE: lua/iron/util/os.lua ================================================ local checks = {} checks.is_windows = function() return package.config:sub(1,1) == '\\' end checks.has = function(feature) return vim.api.nvim_call_function('has', {feature}) == 1 end return checks ================================================ FILE: lua/iron/util/tables.lua ================================================ -- luacheck: globals vim local fns = {} fns.extend = function(...) local tbl = {} local tbls = {n = select("#", ...), ...} for ix=1, tbls.n do local itm = tbls[ix] if itm ~= nil then if type(itm) == "table" then vim.list_extend(tbl, itm) else table.insert(tbl, itm) end end end return tbl end return fns ================================================ FILE: lua/iron/view.lua ================================================ -- luacheck: globals unpack vim local view = {} --- Private functions local with_defaults = function(options) return vim.tbl_extend("keep", options or {}, { winfixwidth = true, winfixheight = true }) end local nested_modifier local with_nested_metatable = function(tbl) return setmetatable(tbl, { __index = nested_modifier, __call = function(_, arg, options) return tbl:mode(arg, options) end }) end nested_modifier = function(tbl, key) local new = vim.deepcopy(tbl) table.insert(new, key) return with_nested_metatable(new) end local size_extractor = function(size, vertical, ...) local new_size if type(size) == "string" and string.find(size, "%%") then local pct = size:gsub("%%", "") pct = tonumber(pct) local base if vertical then base = vim.o.columns else base = vim.o.lines end new_size = math.floor((base * pct) / 100.0) elseif type(size) == "function" then new_size = size(vertical, ...) elseif size == nil then new_size = 0 elseif 1 > size and size > 0 then local base if vertical then base = vim.o.columns else base = vim.o.lines end new_size = math.floor(base * size) else new_size = size end return new_size end view.helpers = {} --- Returns proportional difference between an object and the editor size -- with the offset adjusting the distance on both sides. -- Use offset 0.5 for centralization -- @tparam number offset proportion of distribution (0.5 is centralized) -- @tparam boolean vertical Whether the oritentation is vertical or not -- @tparam number sz size of the object -- @treturn number placement index -- @treturn function view.helpers.proportion = function(offset) return function(vertical, sz) local attr = vertical and "columns" or "lines" return math.ceil(vim.o[attr] * offset - sz * offset) end end --- Flips the orientation from top/left to bottom/right -- @tparam number offset in columns/lines -- @tparam boolean vertical Whether the oritentation is vertical or not -- @tparam number sz size of the object -- @treturn number placement index -- @treturn function view.helpers.flip = function(offset) return function(vertical, sz) local attr = vertical and "columns" or "lines" return math.ceil(vim.o[attr] - sz - offset) end end --- Open a split window -- Takes the arguments to the split command as nested values (keys) of this table. -- @example view.split.vertical.botright(20) -- @tparam table data itself -- @tparam number|string|function size Either a number, a size in string or a function that returns the size -- @tparam number bufnr buffer handle -- @treturn number window id -- @treturn function the function that opens the window view.split = with_nested_metatable{ mode = function(data, size, options) return function(bufnr) local args = vim.list_slice(data, 1, #data) local new_size = size_extractor(size, vim.tbl_contains(data, "vertical") or vim.tbl_contains(data, "vert")) if size then table.insert(args, tostring(new_size)) end table.insert(args, "split") vim.cmd(table.concat(args, " ")) vim.api.nvim_set_current_buf(bufnr) local winid = vim.fn.bufwinid(bufnr) for opt, val in pairs(with_defaults(options)) do vim.api.nvim_win_set_option(winid, opt, val) end return winid end end } --- Used to open a float window -- @tparam table config parameters for the float window -- @tparam number buff buffer handle -- @treturn number window id view.openfloat = function(config, buff) return vim.api.nvim_open_win(buff, false, config) end --- Opens a float at any point in the window -- @tparam table opts Options for calculating the repl size -- @tparam number|string|function opts.width width of the window -- @tparam number|string|function opts.height height of the window -- @tparam number|string|function opts.w_offset horizontal offset from the bottom-right corner -- @tparam number|string|function opts.h_offset vertical offset from the bottom-right corner -- @treturn table configuration for the float window -- @treturn function view.offset = function(opts) return function() local new_w_size = size_extractor(opts.width, true) local new_h_size = size_extractor(opts.height, false) local new_w_offset = size_extractor(opts.w_offset, true, new_w_size) local new_h_offset = size_extractor(opts.h_offset, false, new_h_size) return { relative = "editor", width = new_w_size, height = new_h_size, row = new_h_offset, col = new_w_offset } end end --- Opens a float pinned to the top -- @tparam number|string|function size height of the window -- @treturn function view.top = function(size) return view.offset{width = vim.o.columns, height = size} end --- Opens a float pinned to the bottom -- @tparam number|string|function size height of the window -- @treturn function view.bottom = function(size) return view.offset{width = vim.o.columns, height = size, h_offset = view.helpers.flip(0)} end --- Opens a float pinned to the right -- @tparam number|string|function size width of the window -- @treturn function view.right = function(size) return view.offset{width = size, height = vim.o.lines, w_offset = view.helpers.flip(0)} end --- Opens a float pinned to the left -- @tparam number|string|function size width of the window -- @treturn function view.left = function(size) return view.offset{width = size, height = vim.o.lines} end --- Opens a repl in the middle of the screen -- @tparam number|string|function width width of the window -- @tparam number|string|function height height of the window. If null will use `width` for this size -- @treturn function view.center = function(width, height) return view.offset{ width = width, height = height or width, w_offset = view.helpers.proportion(0.5), h_offset = view.helpers.proportion(0.5) } end view.curry = setmetatable({}, { __index = function(_, key) if view[key] == nil then error("Function `view." .. key .. "` does not exist.") end vim.deprecate("view.curry." .. key, "view." .. key, "3.2", "iron.nvim") return view[key] end }) return view ================================================ FILE: lua/iron/visibility.lua ================================================ -- luacheck: globals unpack vim local visibility = {} --- Show hidden window -- Creates a window for a buffer if it was previously -- hidden by nvim_win_hide, otherwise does nothing. -- @return window handle -- @treturn boolean wither the window was hidden or not local show_hidden = function(bufid, showfn) local was_hidden = false local window = vim.fn.bufwinid(bufid) if window == -1 then was_hidden = true window = showfn() end return window, was_hidden end visibility.single = function(bufid, showfn) show_hidden(bufid, showfn) end visibility.toggle = function(bufid, showfn) local window, was_hidden = show_hidden(bufid, showfn) if not was_hidden then vim.api.nvim_win_hide(window) end end visibility.focus = function(bufid, showfn) local window = show_hidden(bufid, showfn) vim.api.nvim_set_current_win(window) end return visibility ================================================ FILE: spec/iron/core_spec.lua ================================================ -- luacheck: globals insulate setup describe it assert mock -- luacheck: globals before_each after_each insulate("About #iron functionality", function() local tbl = require('iron.util.tables') before_each(function() _G.vim = mock({ api = { nvim_call_function = function(_, _) return 1 end, nvim_command = function(_) return "" end, nvim_get_option = function(_) return "" end, nvim_get_var = function(_) return "" end, nvim_create_namespace = function(_) return 1000 end, nvim_set_hl_ns = function(_) return nil end, nvim_set_hl = function(_) return nil end, nvim_err_writeln = function(_) return nil end, }, fn = { executable = function(_) return true end } }) _G.os = mock({ execute = function(_) return 0 end, }) end) after_each(function() package.loaded['iron'] = nil package.loaded['iron.config'] = nil package.loaded['iron.core'] = nil end) describe("dynamic #config", function() it("is not called on a stored config", function() local iron = require('iron') iron.config.stuff = 1 local _ = iron.config.stuff assert.stub(_G.vim.api.nvim_get_var).was_called(0) end) end) describe("#core functions", function() it("repl_for", function() local iron = require('iron') iron.ll.ensure_repl_exists = mock(function() return {bufnr = 1}, true end) iron.core.repl_for("python") assert.spy(_G.vim.api.nvim_command).was_called(1) end) it("repl_for if repl exists", function() local iron = require('iron') iron.ll.ensure_repl_exists = mock(function() return {bufnr = 1}, true end) iron.config.visibility = mock(function() end) iron.core.repl_for("python") assert.spy(_G.vim.api.nvim_command).was_called(1) assert.spy(iron.config.visibility).was_called(0) iron.ll.ensure_repl_exists = mock(function() return {bufnr = 1}, false end) iron.core.repl_for("python") assert.spy(iron.config.visibility).was_called(1) end) end) end) ================================================ FILE: spec/iron/fts/common_spec.lua ================================================ -- luacheck: globals insulate setup describe it assert mock -- luacheck: globals before_each after_each insulate("On fthelper", function() local fts = require("iron.fts.common").functions describe("the #format function", function() it("should fail on string input", function() assert.has.errors(function () fts.format({}, "") end) end) it("should not add a new line on #empty lines input", function() assert.are.same(fts.format({}, {}), {}) end) it("should not add a new empty line if the last one is an #empty line", function() assert.are.same(fts.format({}, {"asdf", ""}), {"asdf", "\13"}) end) it("should not add a new line if the input contains only one line", function() assert.are.same(fts.format({}, {"asdf"}), {"asdf\13"}) end) it("should add a new line if the input contains more than one line", function() assert.are.same(fts.format({}, {"asdf", "qwer"}), {"asdf", "qwer\13"}) end) it("should use supplied #format function if one is supplied", function() local f = mock(function(lns) return lns end) assert.are.same(fts.format({format = f}, {"1", "2"}), {"1", "2"}) assert.stub(f).was_called(1) end) it("should #wrap the lines with whatever supplied enclosing pairs", function() assert.are.same( fts.format({open = "{", close = "}"}, {"asdf", "qwer"}), {"{", "asdf", "qwer", "}\13"} ) assert.are.same( fts.format({open = "\x1b[200~", close = "\x1b[201~"}, {"asdf", "qwer"}), {"\x1b[200~", "asdf", "qwer", "\x1b[201~\13"} ) assert.are.same( fts.format({open = "\27[200~", close = "\27[201~"}, {"asdf", "qwer"}), {"\27[200~", "asdf", "qwer", "\27[201~\13"} ) end) end) end) ================================================ FILE: spec/iron/util/tables_spec.lua ================================================ -- luacheck: globals insulate setup describe it assert mock -- luacheck: globals before_each after_each insulate("On #tables code", function() local fn = require('iron.util.tables') describe("#get", function() it("should return nil if key doesn't exist", function() assert.are_same(nil, fn.get({}, "key")) end) it("should return nil if map is nil", function() assert.are_same(nil, fn.get(nil, "key")) end) it("should return nil if map is not a table", function() assert.are_same(nil, fn.get(0, "key")) end) it("should return value otherwise", function() assert.are_same(0, fn.get({key = 0}, "key")) end) end) end)