Skip to content

Commit

Permalink
feat: Output as Virtual Text (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
benlubas committed Nov 17, 2023
1 parent f7871bd commit 820463d
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 117 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ variable, their values, and a brief description.
| `g:molten_output_win_style` | (`false`) \| `"minimal"` | Value passed to the `style` option in `:h nvim_open_win()` |
| `g:molten_save_path` | (`stdpath("data").."/molten"`) \| any path to a folder | Where to save/load data with `:MoltenSave` and `:MoltenLoad` |
| `g:molten_use_border_highlights` | `true` \| (`false`) | When true, uses different highlights for output border depending on the state of the cell (running, done, error). see [highlights](#highlights) |
| `g:molten_virt_lines_off_by_1` | `true` \| (`false`) | _only has effect when `output_virt_lines` is true_ Allows the output window to cover exactly one line of the regular buffer. (useful for running code in a markdown file where that covered line will just be \`\`\`) |
| `g:molten_virt_lines_off_by_1` | `true` \| (`false`) | Allows the output window to cover exactly one line of the regular buffer when `output_virt_lines` is true, also effects `virt_text_output`. (useful for running code in a markdown file where that covered line will just be \`\`\`) |
| `g:molten_virt_text_output` | `true` \| (`false`) | When true, show output as virtual text below the cell. When true, output window doesn't open automatically on run. Effected by `virt_lines_off_by_1` |
| `g:molten_virt_text_max_lines` | (`12`) \| int | Max height of the virtual text |
| `g:molten_wrap_output` | `true` \| (`false`) | Wrap text in output windows |
| [DEBUG] `g:molten_show_mimetype_debug` | `true` \| (`false`) | Before any non-iostream output chunk, the mime-type for that output chunk is shown. Meant for debugging/plugin devlopment |

Expand Down Expand Up @@ -205,6 +207,7 @@ Here is a complete list of the highlight groups that Molten uses, and their defa
- `MoltenOutputWinNC` = `MoltenOutputWin`: a "Non-Current" output window
- `MoltenOutputFooter` = `FloatFooter`: the "x more lines" text
- `MoltenCell` = `CursorLine`: applied to code that makes up a cell
- `MoltenVirtualText` = `Comment`: output that is rendered as virtual text

## Autocommands

Expand Down
45 changes: 5 additions & 40 deletions lua/output_window.lua
Original file line number Diff line number Diff line change
@@ -1,49 +1,14 @@

local M = {}

---Calculate the y position of the output window, accounting for folds, extmarks, and scroll.
---@param buf number
---Calculate the y position of the output window
---@param buf_line number
---@return number
M.calculate_window_position = function(buf, buf_line)
-- code modified from image.nvim https://github.com/3rd/image.nvim/blob/16f54077ca91fa8c4d1239cc3c1b6663dd169092/lua/image/renderer.lua#L254
local win_top = vim.fn.line("w0")
if win_top == nil then return buf_line end
local offset = 0
M.calculate_window_position = function(buf_line)
local win = vim.api.nvim_get_current_win()
local pos = vim.fn.screenpos(win, buf_line, 0)

if vim.wo.foldenable then
local i = win_top
while i <= buf_line do
local fold_start, fold_end = vim.fn.foldclosed(i), vim.fn.foldclosedend(i)

if fold_start ~= -1 and fold_end ~= -1 then
offset = offset + (fold_end - fold_start)
i = fold_end + 1
else
i = i + 1
end
end
end

local extmarks = vim.tbl_map(
function(mark)
local mark_id, mark_row, mark_col, mark_opts = unpack(mark)
local virt_height = #(mark_opts.virt_lines or {})
return { id = mark_id, row = mark_row, col = mark_col, height = virt_height }
end,
vim.api.nvim_buf_get_extmarks(
buf,
-1,
{ win_top - 1, 0 },
{ buf_line - 1, 0 },
{ details = true }
)
)
for _, mark in ipairs(extmarks) do
if mark.row + 1 ~= buf_line then offset = offset - mark.height end
end

return buf_line - win_top + 1 - offset
return pos.row
end

return M
4 changes: 2 additions & 2 deletions rplugin/python3/molten/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ def command_show_output(self) -> None:

for molten in molten_kernels:
if molten.current_output is not None:
molten.should_show_display_window = True
molten.should_show_floating_win = True
self._update_interface()
return

Expand All @@ -672,7 +672,7 @@ def command_hide_output(self) -> None:
return

for molten in molten_kernels:
molten.should_show_display_window = False
molten.should_show_floating_win = False

self._update_interface()

Expand Down
12 changes: 4 additions & 8 deletions rplugin/python3/molten/images.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Dict, Set
from abc import ABC, abstractmethod

from pynvim import Nvim, logging
from pynvim import Nvim

from molten.utils import notify_warn


class Canvas(ABC):
Expand Down Expand Up @@ -101,8 +103,6 @@ def add_image(
pass


# I think this class will end up being calls to equivalent lua functions in some lua file
# somewhere
class ImageNvimCanvas(Canvas):
nvim: Nvim
to_make_visible: Set[str]
Expand Down Expand Up @@ -180,9 +180,5 @@ def get_canvas_given_provider(name: str, nvim: Nvim) -> Canvas:
elif name == "image.nvim":
return ImageNvimCanvas(nvim)
else:
nvim.api.notify(
f"[Molten] unknown image provider: `{name}`",
logging.ERROR,
{"title": "Molten"},
)
notify_warn(nvim, f"unknown image provider: `{name}`")
return NoCanvas()
59 changes: 32 additions & 27 deletions rplugin/python3/molten/moltenbuffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MoltenKernel:
queued_outputs: "Queue[CodeCell]"

selected_cell: Optional[CodeCell]
should_show_display_window: bool
should_show_floating_win: bool
updating_interface: bool

options: MoltenOptions
Expand Down Expand Up @@ -66,7 +66,7 @@ def __init__(
self.queued_outputs = Queue()

self.selected_cell = None
self.should_show_display_window = False
self.should_show_floating_win = False
self.updating_interface = False

self.options = options
Expand All @@ -90,24 +90,24 @@ def restart(self, delete_outputs: bool = False) -> None:
if delete_outputs:
self.outputs = {}
self.clear_interface()
self.clear_open_output_windows()

self.runtime.restart()

def run_code(self, code: str, span: CodeCell) -> None:
self.delete_overlapping_cells(span)
self.runtime.run_code(code)

if span in self.outputs:
self.outputs[span].clear_interface()
del self.outputs[span]

self.outputs[span] = OutputBuffer(
self.nvim, self.canvas, self.extmark_namespace, self.options
)
self.queued_outputs.put(span)

self.selected_cell = span
self.should_show_display_window = True

if not self.options.virt_text_output:
self.should_show_floating_win = True

self.update_interface()

self._check_if_done_running()
Expand Down Expand Up @@ -148,8 +148,8 @@ def tick(self) -> None:
def enter_output(self) -> None:
if self.selected_cell is not None:
if self.options.enter_output_behavior != "no_open":
self.should_show_display_window = True
self.should_show_display_window = self.outputs[self.selected_cell].enter(
self.should_show_floating_win = True
self.should_show_floating_win = self.outputs[self.selected_cell].enter(
self.selected_cell.end
)

Expand All @@ -171,7 +171,7 @@ def clear_interface(self) -> None:

def clear_open_output_windows(self) -> None:
for output in self.outputs.values():
output.clear_interface()
output.clear_float_win()

def _get_selected_span(self) -> Optional[CodeCell]:
current_position = self._get_cursor_position()
Expand All @@ -189,7 +189,8 @@ def delete_overlapping_cells(self, span: CodeCell) -> None:
if output_span.overlaps(span):
if self.current_output == output_span:
self.current_output = None
self.outputs[output_span].clear_interface()
self.outputs[output_span].clear_float_win()
self.outputs[output_span].clear_virt_output(span.bufno)
del self.outputs[output_span]
output_span.clear_interface(self.highlight_namespace)

Expand All @@ -198,7 +199,7 @@ def delete_cell(self) -> None:
if self.selected_cell is None:
return

self.outputs[self.selected_cell].clear_interface()
self.outputs[self.selected_cell].clear_float_win()
self.selected_cell.clear_interface(self.highlight_namespace)
del self.outputs[self.selected_cell]
self.selected_cell = None
Expand All @@ -212,40 +213,44 @@ def update_interface(self) -> None:
return

self.updating_interface = True
selected_cell = self._get_selected_span()
new_selected_cell = self._get_selected_span()

# Clear the cell we just left
if self.selected_cell != selected_cell and self.selected_cell is not None:
if self.selected_cell != new_selected_cell and self.selected_cell is not None:
if self.selected_cell in self.outputs:
self.outputs[self.selected_cell].clear_interface()
self.outputs[self.selected_cell].clear_float_win()
self.selected_cell.clear_interface(self.highlight_namespace)

if selected_cell is None:
self.should_show_display_window = False
if new_selected_cell is None:
self.should_show_floating_win = False

self.selected_cell = selected_cell
self.selected_cell = new_selected_cell

if self.selected_cell is not None:
self._show_selected(self.selected_cell)
self.canvas.present()

if self.options.virt_text_output:
for span, output in self.outputs.items():
output.show_virtual_output(span.end)

self.updating_interface = False

def on_cursor_moved(self, scrolled=False) -> None:
selected_cell = self._get_selected_span()
new_selected_cell = self._get_selected_span()

if (
self.selected_cell is None
and selected_cell is not None
and new_selected_cell is not None
and self.options.auto_open_output
):
self.should_show_display_window = True
self.should_show_floating_win = True

if self.selected_cell == selected_cell and selected_cell is not None:
if self.selected_cell == new_selected_cell and new_selected_cell is not None:
if (
scrolled
and selected_cell.end.lineno < self.nvim.funcs.line("w$")
and self.should_show_display_window
and new_selected_cell.end.lineno < self.nvim.funcs.line("w$")
and self.should_show_floating_win
):
self.update_interface()
return
Expand Down Expand Up @@ -294,10 +299,10 @@ def _show_selected(self, span: CodeCell) -> None:
span.end.colno,
)

if self.should_show_display_window:
self.outputs[span].show(span.end)
if self.should_show_floating_win:
self.outputs[span].show_floating_win(span.end)
else:
self.outputs[span].clear_interface()
self.outputs[span].clear_float_win()

def _get_content_checksum(self) -> str:
return hashlib.md5(
Expand Down
10 changes: 8 additions & 2 deletions rplugin/python3/molten/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class HL:
win_nc = "MoltenOutputWinNC"
foot = "MoltenOutputFooter"
cell = "MoltenCell"
virtual_text = "MoltenVirtualText"

defaults = {
border_norm: "FloatBorder",
Expand All @@ -25,6 +26,7 @@ class HL:
win_nc: win,
foot: "FloatFooter",
cell: "CursorLine",
virtual_text: "Comment",
}


Expand All @@ -35,17 +37,19 @@ class MoltenOptions:
image_provider: str
output_crop_border: bool
output_show_more: bool
output_virt_lines: bool
output_win_border: Union[str, List[str]]
output_win_cover_gutter: bool
output_win_hide_on_leave: bool
output_win_max_height: int
output_win_max_width: int
output_win_style: Optional[str]
output_virt_lines: bool
save_path: str
show_mimetype_debug: bool
use_border_highlights: bool
virt_lines_off_by_1: bool
virt_text_max_lines: int
virt_text_output: bool
wrap_output: bool
nvim: Nvim
hl: HL
Expand All @@ -61,17 +65,19 @@ def __init__(self, nvim: Nvim):
("molten_image_provider", "none"),
("molten_output_crop_border", True),
("molten_output_show_more", False),
("molten_output_virt_lines", False),
("molten_output_win_border", [ "", "━", "", "" ]),
("molten_output_win_cover_gutter", True),
("molten_output_win_hide_on_leave", True),
("molten_output_win_max_height", 999999),
("molten_output_win_max_width", 999999),
("molten_output_win_style", False),
("molten_output_virt_lines", False),
("molten_save_path", os.path.join(nvim.funcs.stdpath("data"), "molten")),
("molten_show_mimetype_debug", False),
("molten_use_border_highlights", False),
("molten_virt_lines_off_by_1", False),
("molten_virt_text_max_lines", 12),
("molten_virt_text_output", False),
("molten_wrap_output", False),
]
# fmt: on
Expand Down
Loading

0 comments on commit 820463d

Please sign in to comment.