Skip to content

Commit

Permalink
Merge pull request #5 from kiyoon/feat-windows-compat
Browse files Browse the repository at this point in the history
use persist queue for cross platform (native Windows support)
  • Loading branch information
kiyoon authored Jan 13, 2023
2 parents bc23b48 + 62feded commit 7bfa680
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 63 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/src/jupynium/jupynium_pid.txt
/src/jupynium/jupynium_persist_queue
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python

Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
<a href="https://github.com/kiyoon/jupynium.nvim/actions/workflows/tests.yml">
<img src="https://github.com/kiyoon/jupynium.nvim/workflows/Tests/badge.svg?style=flat" />
<img src="https://github.com/kiyoon/jupynium.nvim/workflows/Tests/badge.svg?style=flat" />
</a>

Jupynium uses Selenium to automate Jupyter Notebook, synchronising everything you type on Neovim.
Never leave Neovim. Switch tabs on the browser as you switch files on Neovim.

<img src=https://user-images.githubusercontent.com/12980409/211894627-73037e83-4730-4387-827c-98ed2522740d.gif width=100% />

Note that it doesn't sync from Notebook to Neovim so be careful.
Note that it doesn't sync from Notebook to Neovim so only modify from Neovim.

### How does it work?

Expand All @@ -27,7 +27,7 @@ Only supports Jupyter Notebook (Jupyter Lab disables front-end interaction)

### Requirements

- 💻 Linux, macOS and Windows WSL2
- 💻 Linux, macOS and Windows (CMD, PowerShell, WSL2)
- 🦊 Firefox (Other browsers are not supported due to their limitation with Selenium)
- ✌️ Neovim >= v0.8
- 🐍 Python >= 3.10
Expand All @@ -40,6 +40,7 @@ conda activate jupynium && conda install -c conda-forge python=3.11
```

Install with vim-plug:

```vim
Plug 'kiyoon/jupynium.nvim', { 'do': 'pip3 install --user .' }
" Plug 'kiyoon/jupynium.nvim', { 'do': '~/miniconda3/bin/envs/jupynium/bin/pip install .' }
Expand All @@ -55,6 +56,7 @@ use { "rcarriga/nvim-notify" } -- optional
```

Install with 💤lazy.nvim

```lua
{
"kiyoon/jupynium.nvim",
Expand All @@ -66,6 +68,7 @@ Install with 💤lazy.nvim
```

Setup is optional for system python users and here are the defaults. Conda users need to change the `python_host`.

<details>
<summary>
See setup defaults
Expand Down Expand Up @@ -162,7 +165,7 @@ For example:

### Open and attach to a Jupynium server

**This is for local neovim only. For remote neovim, see [Command-Line Usage](#%EF%B8%8F-command-line-usage-attach-to-remote-neovim).**
**This is for local neovim only. For remote neovim, see [Command-Line Usage](#%EF%B8%8F-command-line-usage-attach-to-remote-neovim).**

Running `:JupyniumStartAndAttachToServer` will open the notebook.
Type password and once **you need to be on the main page (file browser) for the next steps**.
Expand Down
61 changes: 33 additions & 28 deletions lua/jupynium/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -104,46 +104,51 @@ function M.add_commands()
vim.api.nvim_create_user_command("JupyniumAttachToServer", M.attach_to_server_cmd, { nargs = "?" })
end

local function call_jupynium_cli_bg(args)
local call_str
if vim.fn.has "win32" == 1 then
call_str = [[call system('PowerShell "Start-Process -FilePath \"]]
.. vim.fn.expand(options.opts.python_host):gsub("\\", "\\\\")
.. [[\" -ArgumentList \"-m jupynium --nvim_listen_addr ]]
.. vim.v.servername

for _, v in ipairs(args) do
call_str = call_str .. [[ `\"]] .. v:gsub("\\", "\\\\") .. [[`\"]]
end

call_str = call_str .. [[\""')]]
else
call_str = [[call system('"]]
.. vim.fn.expand(options.opts.python_host)
.. [[" -m jupynium --nvim_listen_addr ]]
.. vim.v.servername

for _, v in ipairs(args) do
call_str = call_str .. [[ "]] .. v:gsub("\\", "\\\\") .. [["]]
end

call_str = call_str .. [[ &')]]
end
vim.cmd(call_str)
end

function M.start_and_attach_to_server_cmd(args)
local notebook_URL = vim.fn.trim(args.args)

if notebook_URL == "" then
vim.cmd(
[[call system(']]
.. options.opts.python_host
.. [[ -m jupynium --notebook_URL ]]
.. options.opts.default_notebook_URL
.. [[ --nvim_listen_addr ' . v:servername . ' &')]]
)
call_jupynium_cli_bg { "--notebook_URL", options.opts.default_notebook_URL }
else
vim.cmd(
[[call system(']]
.. options.opts.python_host
.. [[ -m jupynium --notebook_URL ]]
.. notebook_URL
.. [[ --nvim_listen_addr ' . v:servername . ' &')]]
)
call_jupynium_cli_bg { "--notebook_URL", notebook_URL }
end
end

function M.attach_to_server_cmd(args)
local notebook_URL = vim.fn.trim(args.args)

if notebook_URL == "" then
vim.cmd(
[[call system(']]
.. options.opts.python_host
.. [[ -m jupynium --attach_only --notebook_URL ]]
.. options.opts.default_notebook_URL
.. [[ --nvim_listen_addr ' . v:servername . ' &')]]
)
call_jupynium_cli_bg { "--attach_only", "--notebook_URL", options.opts.default_notebook_URL }
else
vim.cmd(
[[call system(']]
.. options.opts.python_host
.. [[ -m jupynium --attach_only --notebook_URL ]]
.. notebook_URL
.. [[ --nvim_listen_addr ' . v:servername . ' &')]]
)
call_jupynium_cli_bg { "--attach_only", "--notebook_URL", notebook_URL }
end
end

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ packaging==22.0
coloredlogs==15.0.1
verboselogs==1.7
pynvim==0.4.3
sysv-ipc==1.1.0
psutil==5.9.4
persist-queue==0.8.0
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ install_requires =
verboselogs >= 1.7
selenium >= 4.7.2
packaging >= 22.0
sysv-ipc >= 1.1.0
psutil >= 5.9.4
persist-queue >= 0.8.0
python_requires = >=3.10
package_dir =
=src
Expand Down
48 changes: 23 additions & 25 deletions src/jupynium/cmds/jupynium.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@
import json
import logging
import os
from pathlib import Path
import sys
import traceback
from pathlib import Path

import coloredlogs
import persistqueue
import verboselogs
from persistqueue.exceptions import Empty
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import WebDriverException
import sysv_ipc
import verboselogs

from .. import __version__
from .. import selenium_helpers as sele
from ..definitions import IPC_KEY, IPC_TYPE_ATTACH_NEOVIM
from ..definitions import persist_queue_path
from ..events_control import process_events
from ..nvim import NvimInfo
from ..process import already_running_pid
from ..pynvim_helpers import attach_and_init
from ..nvim import NvimInfo
from .. import __version__

logger = verboselogs.VerboseLogger(__name__)

Expand Down Expand Up @@ -92,10 +93,10 @@ def get_parser():
return parser


def start_if_running_else_clear(args, ipc_queue):
def start_if_running_else_clear(args, q: persistqueue.UniqueQ):
# If Jupynium is already running, send args and quit.
if already_running_pid():
ipc_queue.send(json.dumps(args.__dict__), True, type=IPC_TYPE_ATTACH_NEOVIM)
q.put(args)
logger.info("Jupynium is already running. Attaching to the running process.")
return 0
else:
Expand All @@ -108,8 +109,8 @@ def start_if_running_else_clear(args, ipc_queue):
# If Jupynium is not running, clear the message queue before starting.
while True:
try:
_, _ = ipc_queue.receive(block=False, type=IPC_TYPE_ATTACH_NEOVIM)
except sysv_ipc.BusyError:
_ = q.get(block=False)
except Empty:
break

return None
Expand All @@ -122,28 +123,28 @@ def attach_new_neovim(
URL_to_home_windows: dict[str, str],
):
logger.info(f"New nvim wants to attach: {new_args}")
if new_args["nvim_listen_addr"] in nvims:
if new_args.nvim_listen_addr in nvims:
logger.info("Already attached.")
else:
try:
nvim = attach_and_init(new_args["nvim_listen_addr"])
if new_args["notebook_URL"] in URL_to_home_windows.keys():
home_window = URL_to_home_windows[new_args["notebook_URL"]]
nvim = attach_and_init(new_args.nvim_listen_addr)
if new_args.notebook_URL in URL_to_home_windows.keys():
home_window = URL_to_home_windows[new_args.notebook_URL]
else:
prev_num_windows = len(driver.window_handles)
driver.switch_to.new_window("tab")
driver.get(new_args["notebook_URL"])
driver.get(new_args.notebook_URL)

# Wait for the notebook to load
driver_wait = WebDriverWait(driver, 10)
driver_wait.until(EC.number_of_windows_to_be(prev_num_windows + 1))
sele.wait_until_loaded(driver)

home_window = driver.current_window_handle
URL_to_home_windows[new_args["notebook_URL"]] = home_window
URL_to_home_windows[new_args.notebook_URL] = home_window

nvim_info = NvimInfo(nvim, home_window)
nvims[new_args["nvim_listen_addr"]] = nvim_info
nvims[new_args.nvim_listen_addr] = nvim_info
except Exception:
logger.exception("Exception occurred while attaching a new nvim. Ignoring.")

Expand Down Expand Up @@ -178,8 +179,8 @@ def main():
print(pid)
sys.exit(0)

ipc_queue = sysv_ipc.MessageQueue(IPC_KEY, sysv_ipc.IPC_CREAT)
return_code = start_if_running_else_clear(args, ipc_queue)
q = persistqueue.UniqueQ(persist_queue_path)
return_code = start_if_running_else_clear(args, q)
if return_code is not None:
sys.exit(return_code)

Expand Down Expand Up @@ -239,13 +240,10 @@ def main():

# Check if a new newvim instance wants to attach to this server.
try:
message, _ = ipc_queue.receive(
block=False, type=IPC_TYPE_ATTACH_NEOVIM
)
except sysv_ipc.BusyError:
new_args = q.get(block=False)
except Empty:
pass
else:
new_args = json.loads(message.decode())
attach_new_neovim(driver, new_args, nvims, URL_to_home_windows)
except WebDriverException:
break
Expand Down
5 changes: 1 addition & 4 deletions src/jupynium/definitions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import os
from pathlib import Path

import sysv_ipc

PACKAGE_DIR = Path(os.path.dirname(os.path.abspath(__file__)))

IPC_KEY = 32985427282198 % sysv_ipc.KEY_MAX
IPC_TYPE_ATTACH_NEOVIM = 1
persist_queue_path = PACKAGE_DIR / "jupynium_persist_queue"

0 comments on commit 7bfa680

Please sign in to comment.