diff --git a/.gitignore b/.gitignore index b6b693b..a0942c0 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index 792a7cd..5603420 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) - + Jupynium uses Selenium to automate Jupyter Notebook, synchronising everything you type on Neovim. @@ -10,7 +10,7 @@ Never leave Neovim. Switch tabs on the browser as you switch files on Neovim. -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? @@ -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 @@ -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 .' } @@ -55,6 +56,7 @@ use { "rcarriga/nvim-notify" } -- optional ``` Install with 💤lazy.nvim + ```lua { "kiyoon/jupynium.nvim", @@ -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`. +
See setup defaults @@ -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**. diff --git a/lua/jupynium/server.lua b/lua/jupynium/server.lua index 4c4bf0b..22fe71b 100644 --- a/lua/jupynium/server.lua +++ b/lua/jupynium/server.lua @@ -104,24 +104,41 @@ 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 @@ -129,21 +146,9 @@ 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 diff --git a/requirements.txt b/requirements.txt index d15527c..a1d91c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/setup.cfg b/setup.cfg index 15472be..6dd46bf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/src/jupynium/cmds/jupynium.py b/src/jupynium/cmds/jupynium.py index ef15082..11dfcbf 100644 --- a/src/jupynium/cmds/jupynium.py +++ b/src/jupynium/cmds/jupynium.py @@ -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__) @@ -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: @@ -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 @@ -122,17 +123,17 @@ 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) @@ -140,10 +141,10 @@ def attach_new_neovim( 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.") @@ -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) @@ -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 diff --git a/src/jupynium/definitions.py b/src/jupynium/definitions.py index 8a6c74b..aa97bc1 100644 --- a/src/jupynium/definitions.py +++ b/src/jupynium/definitions.py @@ -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"