├── denops └── vim-pluto │ ├── const.ts │ ├── main.ts │ └── funcs.ts ├── .github └── FUNDING.yml ├── autoload └── pluto.vim ├── LICENSE ├── README.md └── doc └── vim-pluto.txt /denops/vim-pluto/const.ts: -------------------------------------------------------------------------------- 1 | export const CELL_HEAD = "# ╔═╡ "; 2 | export const ORDER_TITLE = "# ╔═╡ Cell order:"; 3 | export const ORDER_HEAD = "# " + ".."; 4 | export const SHOWN_HEAD = "# ╠═"; 5 | export const HIDDEN_HEAD = "# ╟─"; 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: hasundue 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /autoload/pluto.vim: -------------------------------------------------------------------------------- 1 | function! pluto#insert_cell_above() abort 2 | return denops#request('vim-pluto', 'insertCellAbove', []) 3 | endfunction 4 | 5 | function! pluto#insert_cell_below() abort 6 | return denops#request('vim-pluto', 'insertCellBelow', []) 7 | endfunction 8 | 9 | function! pluto#show_code() abort 10 | return denops#request('vim-pluto', 'showCode', []) 11 | endfunction 12 | 13 | function! pluto#hide_code() abort 14 | return denops#request('vim-pluto', 'hideCode', []) 15 | endfunction 16 | 17 | function! pluto#toggle_code() abort 18 | return denops#request('vim-pluto', 'toggleCode', []) 19 | endfunction 20 | 21 | function! pluto#delete_cell() abort 22 | return denops#request('vim-pluto', 'deleteCell', []) 23 | endfunction 24 | 25 | function! pluto#yank_cell() abort 26 | return denops#request('vim-pluto', 'yankCell', []) 27 | endfunction 28 | 29 | function! pluto#paste_cell_above() abort 30 | return denops#request('vim-pluto', 'pasteCellAbove', []) 31 | endfunction 32 | 33 | function! pluto#paste_cell_below() abort 34 | return denops#request('vim-pluto', 'pasteCellBelow', []) 35 | endfunction 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shun Ueda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /denops/vim-pluto/main.ts: -------------------------------------------------------------------------------- 1 | import { Denops } from "https://deno.land/x/denops_std@v3.0.0/mod.ts"; 2 | 3 | import { 4 | insertCell, 5 | setCodeVisibility, 6 | yankCell, 7 | pasteCell, 8 | } from "./funcs.ts"; 9 | 10 | export function main(denops: Denops): void { 11 | denops.dispatcher = { 12 | async insertCellAbove(): Promise { 13 | await insertCell(denops, -1); 14 | }, 15 | async insertCellBelow(): Promise { 16 | await insertCell(denops, +1); 17 | }, 18 | 19 | async showCode(): Promise { 20 | await setCodeVisibility(denops, +1); 21 | }, 22 | async hideCode(): Promise { 23 | await setCodeVisibility(denops, -1); 24 | }, 25 | async toggleCode(): Promise { 26 | await setCodeVisibility(denops, 0); 27 | }, 28 | 29 | async deleteCell(): Promise { 30 | await yankCell(denops, true); 31 | }, 32 | async yankCell(): Promise { 33 | await yankCell(denops, false); 34 | }, 35 | async pasteCellAbove(): Promise { 36 | await pasteCell(denops, -1); 37 | }, 38 | async pasteCellBelow(): Promise { 39 | await pasteCell(denops, +1); 40 | }, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-pluto 2 | A plugin for editing [Pluto](https://github.com/fonsp/Pluto.jl) notebooks with Vim. 3 | 4 | Currently, a couple of different versions of the plugin are being developed. 5 | - A minimalistic helper plugin to edit .jl notebook files, which is supposed to interact with Pluto indirectly (main branch) 6 | - A plugin which turns your Vim into a front-end of Pluto, interacting with Pluto directly (dev branch) 7 | 8 | ## Requirements 9 | - Vim 8 / NeoVim 0.5 or newer 10 | - [Deno](https://deno.land) 11 | - [denops.vim](https://github.com/vim-denops/denops.vim) 12 | 13 | ## Installation 14 | 1. Install Deno (see https://deno.land/#installation) 15 | 1. Install denops.vim and vim-pluto with your package manager 16 | 17 | ex. vim-plug 18 | ```viml 19 | Plug 'vim-denops/denops.vim' 20 | Plug 'hasundue/vim-pluto' 21 | ``` 22 | 23 | ## Configuration 24 | vim-pluto does not provide any default key mappings. You have to do it manually. 25 | 26 | Example: 27 | 28 | ```viml 29 | nnoremap O :call pluto#insert_cell_above() 30 | nnoremap o :call pluto#insert_cell_below() 31 | 32 | nnoremap yy :call pluto#yank_cell() 33 | nnoremap dd :call pluto#delete_cell() 34 | 35 | nnoremap P :call pluto#paste_cell_above() 36 | nnoremap p :call pluto#paste_cell_below() 37 | 38 | nnoremap t :call pluto#toggle_code() 39 | ``` 40 | 41 | ## Recommended Usage 42 | Launch Pluto server with auto_reload_from_file = true. 43 | 44 | Example: 45 | 46 | ```julia 47 | import Pluto 48 | Pluto.run(auto_reload_from_file = true) 49 | ``` 50 | 51 | Enable autoread function of Vim. 52 | 53 | Example: 54 | 55 | ```viml 56 | set autoread 57 | autocmd FocusGained,BufEnter,CursorHold,CursorHoldI * if mode() != 'c' | checktime | endif 58 | autocmd FileChangedShellPost * echohl WarningMsg | echo "File changed on disk. Buffer reloaded." | echohl None 59 | ``` 60 | 61 | Edit a notebook (.jl file) in Vim while opening the notebook in Pluto web UI. 62 | 63 | ## Provided Functions 64 | ### Insert a cell 65 | - pluto#insert_cell_above() 66 | - pluto#insert_cell_below() 67 | 68 | Insert an empty cell above/below the cell under the cursor and start editing it. 69 | 70 | ### Yank/cut/paste a cell 71 | - pluto#yank_cell() 72 | - pluto#delete_cell() 73 | 74 | Yank/cut cell under the cursor and put it into the register reserved for the plugin. 75 | 76 | - pluto#paste_cell_above() 77 | - pluto#paste_cell_below() 78 | 79 | Paste a cell in the register above/below the cell under the cursor. 80 | 81 | ### Show/hide code 82 | - pluto#show_code() 83 | - pluto#hide_code() 84 | - pluto#toggle_code() 85 | 86 | Change visibility of code in the cell under the cursor. 87 | 88 | Changes are not reflected in Web UI until a reload of the notebook because Pluto lacks of the implementation, unfortunately. 89 | -------------------------------------------------------------------------------- /doc/vim-pluto.txt: -------------------------------------------------------------------------------- 1 | *vim-pluto.txt* A plugin for editing Pluto notebooks with Vim. 2 | 3 | 4 | Author: Shun Ueda 5 | License: MIT license 6 | 7 | CONTENTS *vim-pluto-contents* 8 | 9 | Introduction |vim-pluto-introduction| 10 | Install |vim-pluto-install| 11 | Keymaps |vim-pluto-keymaps| 12 | Functions |vim-pluto-functions| 13 | 14 | 15 | ============================================================================== 16 | INTRODUCTION *vim-pluto-introduction* 17 | 18 | A minimalistic helper plugin for editing Pluto notebooks with Vim. 19 | 20 | 21 | ============================================================================== 22 | INSTALL *vim-pluto-install* 23 | 24 | vim-pluto depends on "denops.vim". You have to install it to use vim-pluto. 25 | 26 | https://github.com/vim-denops/denops.vim 27 | 28 | 29 | ============================================================================== 30 | KEYMAPS *vim-pluto-keymaps* 31 | 32 | vim-pluto does not provide any default keymappings. 33 | You have to configure by yourself. 34 | 35 | Example: 36 | > 37 | nnoremap O :call pluto#insert_cell_above() 38 | nnoremap o :call pluto#insert_cell_below() 39 | 40 | nnoremap yy :call pluto#yank_cell() 41 | nnoremap dd :call pluto#delete_cell() 42 | 43 | nnoremap P :call pluto#paste_cell_above() 44 | nnoremap p :call pluto#paste_cell_below() 45 | 46 | nnoremap t :call pluto#toggle_code() 47 | < 48 | 49 | ============================================================================== 50 | FUNCTIONS *vim-pluto-functions* 51 | 52 | 53 | ------------------------------------------------------------------------------ 54 | *vim-pluto-functions-insert_cell* 55 | 56 | *vim-pluto#insert_cell_above()* 57 | pluto#insert_cell_above() 58 | Insert an empty cell above the cell under the cursor and start editing it. 59 | 60 | *vim-pluto#insert_cell_below()* 61 | pluto#insert_cell_below() 62 | Insert an empty cell below the cell under the cursor and start editing it. 63 | 64 | 65 | ------------------------------------------------------------------------------ 66 | *vim-pluto-functions-yank_cell* 67 | 68 | *vim-pluto#yank_cell()* 69 | pluto#yank_cell() 70 | Yank a cell under the cursor into the register reserved for the plugin. 71 | 72 | *vim-pluto#delete_cell()* 73 | pluto#delete_cell() 74 | Delete a cell under the cursor and put it into the register reserved 75 | for the plugin. 76 | 77 | 78 | ------------------------------------------------------------------------------ 79 | *vim-pluto-functions-paste_cell* 80 | 81 | *vim-pluto#paste_cell_above()* 82 | pluto#paste_cell_above() 83 | Paste a cell in the register above the cell under the cursor. 84 | 85 | *vim-pluto#paste_cell_below()* 86 | pluto#paste_cell_below() 87 | Paste a cell in the register below the cell under the cursor. 88 | 89 | 90 | ------------------------------------------------------------------------------ 91 | *vim-pluto-functions-hide_cell* 92 | 93 | *vim-pluto#show_code()* 94 | pluto#show_code() 95 | Set visibility status of code in the cell under the cursor as shown. 96 | 97 | *vim-pluto#hide_code()* 98 | pluto#hide_code() 99 | Set visibility status of code in the cell under the cursor as hidden. 100 | 101 | *vim-pluto#toggle_code()* 102 | pluto#toggle_code() 103 | Toggle shown/hidden status of code in the cell under the cursor. 104 | 105 | Note: Changes are not reflected in Web UI until a reload of the notebook because 106 | Pluto lacks of the implementation, unfortunately. 107 | 108 | 109 | ============================================================================== 110 | vim:tw=78:ts=8:ft=help:norl:noet:fen:noet: 111 | -------------------------------------------------------------------------------- /denops/vim-pluto/funcs.ts: -------------------------------------------------------------------------------- 1 | import { Denops } from "https://deno.land/x/denops_std@v3.0.0/mod.ts"; 2 | import * as vim from "https://deno.land/x/denops_std@v3.0.0/function/mod.ts"; 3 | 4 | import { isString, ensureNumber, ensureArray } from "https://deno.land/x/unknownutil@v1.1.4/mod.ts"; 5 | import { v4 } from "https://deno.land/std@0.122.0/uuid/mod.ts"; 6 | 7 | import { 8 | CELL_HEAD, 9 | ORDER_TITLE, 10 | ORDER_HEAD, 11 | SHOWN_HEAD, 12 | HIDDEN_HEAD, 13 | } from "./const.ts"; 14 | 15 | export async function insertCell( 16 | denops: Denops, 17 | direction = +1, 18 | startInsert = true, 19 | cellId = crypto.randomUUID(), 20 | cellLines: string[] = [], 21 | ): Promise { 22 | const cursorPos = await vim.getcurpos(denops); 23 | const cursorLnum = cursorPos[1]; 24 | const lastLnum = await vim.line(denops, "$"); 25 | 26 | const orderTitleLnum = await vim.search(denops, ORDER_TITLE, "n"); 27 | ensureNumber(orderTitleLnum); 28 | 29 | let newOrderLnum: number; 30 | let newLnum: number; 31 | let newCellId: string; 32 | 33 | const headerLnum = direction < 0 34 | ? await vim.search(denops, CELL_HEAD, "cnb") 35 | : await vim.search(denops, CELL_HEAD, "n"); 36 | ensureNumber(headerLnum); 37 | 38 | if ( headerLnum == 0 ) { // No cell found in the file 39 | await vim.append(denops, lastLnum, ORDER_TITLE); 40 | newOrderLnum = lastLnum + 1; 41 | newLnum = direction < 0 ? cursorLnum - 1 : cursorLnum ; 42 | } 43 | else if ( direction * (headerLnum - cursorLnum) < 0 ) { // Top or bottom of the notebook 44 | newOrderLnum = direction < 0 ? orderTitleLnum : lastLnum; 45 | newLnum = direction < 0 ? cursorLnum - 1 : orderTitleLnum - 1; 46 | } 47 | else { 48 | const header: string = await vim.getline(denops, headerLnum); 49 | const maybeId: string = header.substring(CELL_HEAD.length); 50 | 51 | if ( !v4.validate(maybeId) ) { // Metadata of the notebook 52 | newOrderLnum = lastLnum; 53 | newLnum = orderTitleLnum - 1; 54 | } 55 | else { 56 | const orderLnum = await vim.search(denops, ORDER_HEAD + maybeId, "n"); 57 | ensureNumber(orderLnum); 58 | const offset = direction < 0 ? -1 : 0; 59 | newOrderLnum = orderLnum + offset; 60 | newLnum = headerLnum - 1; 61 | } 62 | } 63 | 64 | const newCellHeaderLnum = await vim.search(denops, cellId, "cn"); 65 | if (newCellHeaderLnum == 0) { 66 | newCellId = cellId; 67 | } 68 | else { 69 | newCellId = crypto.randomUUID(); 70 | } 71 | 72 | await vim.append(denops, newOrderLnum, SHOWN_HEAD + newCellId); 73 | 74 | let newCellLines = [CELL_HEAD + newCellId].concat(cellLines); 75 | if (startInsert) { 76 | newCellLines = newCellLines.concat(["", ""]); 77 | } 78 | await vim.append(denops, newLnum, newCellLines); 79 | 80 | if (startInsert) { 81 | await vim.cursor(denops, newLnum + cellLines.length + 2, 1); 82 | await denops.cmd("startinsert"); 83 | } 84 | } 85 | 86 | export async function setCodeVisibility(denops: Denops, mode: number): Promise { 87 | const headerLnum = await vim.search(denops, CELL_HEAD, "cnb"); 88 | ensureNumber(headerLnum); 89 | 90 | const maybeCellHeader = await vim.getline(denops, headerLnum); 91 | const maybeId = maybeCellHeader.substring(CELL_HEAD.length); 92 | 93 | if ( !v4.validate(maybeId) ) { 94 | return; 95 | } 96 | 97 | const orderLnum = await vim.search(denops, ORDER_HEAD + maybeId, "n"); 98 | ensureNumber(orderLnum); 99 | 100 | const orderLine = await vim.getline(denops, orderLnum); 101 | const head = orderLine.substring(0, ORDER_HEAD.length); 102 | 103 | let newHead: string; 104 | 105 | if ( mode > 0 ) { // Show the cell 106 | newHead = SHOWN_HEAD; 107 | } 108 | else if ( mode < 0 ) { // Hide the cell 109 | newHead = HIDDEN_HEAD; 110 | } 111 | else { // Toggle (mode = 0) 112 | newHead = head == SHOWN_HEAD ? HIDDEN_HEAD : SHOWN_HEAD; 113 | } 114 | 115 | const newOrderLine = newHead + orderLine.substring(ORDER_HEAD.length); 116 | await vim.setline(denops, orderLnum, newOrderLine); 117 | } 118 | 119 | export async function yankCell(denops: Denops, del: boolean): Promise { 120 | const headerLnum = await vim.search(denops, CELL_HEAD, "cnb"); 121 | ensureNumber(headerLnum); 122 | 123 | const maybeCellHeader = await vim.getline(denops, headerLnum); 124 | const maybeId = maybeCellHeader.substring(CELL_HEAD.length); 125 | 126 | if ( !v4.validate(maybeId) ) { 127 | return; 128 | } 129 | 130 | const nextHeaderLnum = await vim.search(denops, CELL_HEAD, "n"); 131 | ensureNumber(nextHeaderLnum); 132 | 133 | const cellLines = await vim.getline(denops, headerLnum, nextHeaderLnum-1); 134 | 135 | const bufName = await vim.bufname(denops); 136 | 137 | if (del) { 138 | await vim.deletebufline(denops, bufName, headerLnum, nextHeaderLnum-1); 139 | } 140 | 141 | await vim.setreg(denops, "vim-pluto", cellLines); 142 | 143 | const orderLnum = await vim.search(denops, ORDER_HEAD + maybeId, "n"); 144 | ensureNumber(orderLnum); 145 | 146 | if (del) { 147 | await vim.deletebufline(denops, bufName, orderLnum); 148 | } 149 | } 150 | 151 | export async function pasteCell(denops: Denops, direction = +1): Promise { 152 | const regLines = await vim.getreg(denops, "vim-pluto", 1, true); 153 | ensureArray(regLines, isString); 154 | 155 | const maybeId = regLines[0].substring(CELL_HEAD.length); 156 | 157 | if ( !v4.validate(maybeId) ) { // should not happen 158 | return; 159 | } 160 | 161 | const cellLines = regLines.slice(1); 162 | 163 | insertCell(denops, direction, false, maybeId, cellLines); 164 | } 165 | --------------------------------------------------------------------------------