michaelb/sniprun

Run current python cell

Closed this issue · 4 comments

Is your feature request related to a problem? Please describe.
I would like to be able to run a python cell (delimited by delimiters such as # %%)

Describe the solution you'd like
running :SnipRunCell would (only for filetype python) detect the beginning and end of the cell, and run this section, similarly to the visual mode '<,'>SnipRun

Describe alternatives you've considered
I currently have a shortcut to visually select the current cell, and then I run '<,'>SnipRun manually (but my cursor moves after I exit the visual selection), using the following code:

-- Function to select the current Python cell delimited by # %%
function SelectPythonCell()
  local current_line = vim.fn.line "."
  local start_line = current_line
  local end_line = current_line

  -- Find the start of the section
  while start_line > 1 do
    local line_content = vim.fn.getline(start_line - 1)
    if line_content:match "^# %%%%" then break end
    start_line = start_line - 1
  end

  -- Find the end of the section
  local total_lines = vim.fn.line "$"
  while end_line < total_lines do
    local line_content = vim.fn.getline(end_line + 1)
    if line_content:match "^# %%%%" then break end
    end_line = end_line + 1
  end

  -- Construct the command string with the given start and end lines
  local cmd = string.format("normal! %dGV%dG", start_line, end_line)
  -- Execute the command
  vim.cmd(cmd)
end

Additional context
I'm not knowledgeable enough with the source code of SnipRun to propose a fully functioning solution, so I hope that at least the above piece of code can be useful in this implementation.
Thank you in advance if you decide to implement this :)

While clearly not impossible (such features exist for literate programming language such as markdown/neorg/orgmode), it's always somewhat a mess (in sniprun's code) to implement that properly to some prod-ready extent.

(For example, what if you don't have a closing delimiter and are working on a huge file? Sniprun might choke if it tries to read GBs in order to find the delimiter)

There is a more straightforward solution for you since you're already halfway, see:

https://michaelb.github.io/sniprun/sources/README.html#mappings-recommandations

The 'moving cursor after selection' is a Neovim 'feature' that plagued sniprun for a long time so a workaround was eventually found. Hopefully you can adapt that script-fu into your shortcut, good luck

Oh yeah I see! Well since it's something that I really want, I spent a bit more time on it and finalized my function:

function Run_sniprun_section()
  -- Check if the file type is Python
  if vim.bo.filetype ~= "python" then
    vim.notify("Run current cell is only available in Python files.", vim.log.levels.WARN)
    return
  end

  -- save the cursor position to move back to it later
  local cursor_pos = vim.api.nvim_win_get_cursor(0)

  -- variables to find the cell delimitor
  local current_line = vim.fn.line "."
  local start_line = current_line
  local end_line = current_line

  -- Find the start of the cell
  while start_line > 1 do
    local line_content = vim.fn.getline(start_line - 1)
    if line_content:match "^# %%%%" then break end
    start_line = start_line - 1
  end

  -- Find the end of the cell
  local total_lines = vim.fn.line "$"
  while end_line < total_lines do
    local line_content = vim.fn.getline(end_line + 1)
    if line_content:match "^# %%%%" then break end
    end_line = end_line + 1
  end

  -- Set the visual selection marks
  vim.fn.setpos("'<", { 0, start_line, 1, 0 })
  vim.fn.setpos("'>", { 0, end_line, 1, 0 })

  -- Execute SnipRun on the visual selection
  vim.cmd "'<,'>SnipRun"

  -- Restore the cursor position
  vim.api.nvim_win_set_cursor(0, cursor_pos)
end

Calling it manually with :lua Run_sniprun_section() or with a keybind

I did a similar workaround as you for the cursor moving (saving the position initially, and then moving back to it).
(btw for non-scripted usage of visual mode I agree with the neovim feature to move the cursor, it would just be nice to have in the api the ability to disable the feature for scripting purposes)

Anyway, now to address your concern about the huge files, we can add a confirmation dialog if the number of lines in the cell is let's say above 100 lines (poorly written because of a copy paste towards the end, but it works):

function Run_sniprun_section()
  -- Check if the file type is Python
  if vim.bo.filetype ~= "python" then
    vim.notify("Run current cell is only available in Python files.", vim.log.levels.WARN)
    return
  end

  -- save the cursor position to move back to it later
  local cursor_pos = vim.api.nvim_win_get_cursor(0)

  -- variables to find the cell delimitor
  local current_line = vim.fn.line "."
  local start_line = current_line
  local end_line = current_line

  -- Find the start of the cell
  while start_line > 1 do
    local line_content = vim.fn.getline(start_line - 1)
    if line_content:match "^# %%%%" then break end
    start_line = start_line - 1
  end

  -- Find the end of the cell
  local total_lines = vim.fn.line "$"
  while end_line < total_lines do
    local line_content = vim.fn.getline(end_line + 1)
    if line_content:match "^# %%%%" then break end
    end_line = end_line + 1
  end

  -- Check if cell contains more than 100
  if (end_line - start_line) > 100 then
    -- Define the options for the confirmation dialog
    local choice = { "Yes", "No" }
    -- Show the confirmation dialog
    vim.ui.select(
      choice,
      { prompt = "Cell containing more than 100 lines, do you want to run SnipRun?" },
      function(chosen)
        if chosen ~= "Yes" then
          vim.notify("SnipRun command cancelled.", vim.log.levels.INFO)
          return
        end

        -- Set the visual selection marks
        vim.fn.setpos("'<", { 0, start_line, 1, 0 })
        vim.fn.setpos("'>", { 0, end_line, 1, 0 })

        -- Execute SnipRun on the visual selection
        vim.cmd "'<,'>SnipRun"

        -- Restore the cursor position
        vim.api.nvim_win_set_cursor(0, cursor_pos)
      end
    )
  else
    -- Set the visual selection marks
    vim.fn.setpos("'<", { 0, start_line, 1, 0 })
    vim.fn.setpos("'>", { 0, end_line, 1, 0 })

    -- Execute SnipRun on the visual selection
    vim.cmd "'<,'>SnipRun"

    -- Restore the cursor position
    vim.api.nvim_win_set_cursor(0, cursor_pos)
  end
end

Maybe this is not too far from being prod-ready for other people to use too?

Anyway, now to address your concern about the huge files,

This was but an example of the additional requirements doing this in sniprun would create

it would just be nice to have in the api the ability to disable the feature for scripting purposes

While I can't comment on Neovim's API, sniprun does expose an API that would work well with 'scripting' approaches, and also completely workarounds the cursor-move-when-selecting 'issue'

https://michaelb.github.io/sniprun/sources/README.html#api

While I don't think I'll implement 'code-bloc' detection in Python, you look like you already have a satisfying solution ?

Thanks I missed this in the documentation, this package is really well done!

Yeah i have a satisfying solution, I'll mark the issue "closed"