zyedidia / micro

A modern and intuitive terminal-based text editor
https://micro-editor.github.io
MIT License
25.09k stars 1.17k forks source link

[Feature Request] Live Markdown Preview #2994

Open fakerybakery opened 12 months ago

fakerybakery commented 12 months ago

Hi, Are you planning to add a live markdown preview, perhaps using Glow, but with a synced scroll position? Thanks!

jabbalaci commented 11 months ago

I put together a plugin for that but it's not perfect yet. I would like to ask the help of the community. Here it is:

VERSION = "0.1.0"

local micro = import("micro")
local config = import("micro/config")
local buffer = import("micro/buffer")

local function mysplit(inputstr, sep)
    if sep == nil then
        sep = "%s"
    end
    local t = {}
    for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
        table.insert(t, str)
    end
    return t
end

function markdownPreview(bp)
    --for determining the file type
    local buf = bp.Buf

    --defensive programming
    if buf:FileType() ~= "markdown" then
        micro.InfoBar():Error("not a markdown file")
        return
    end

    --create buffer
    local tmpBuffer = buffer.NewBuffer("", "/tmp/micro_markdownPreview")

    --open split
    local tmpBufferpane = bp:VSplitBuf(tmpBuffer)

    local cmd = string.format("wgo -file=%s clear :: glow %s", buf.Path, buf.Path)
    local jobArgs = mysplit(cmd)

    --open terminal
    tmpBufferpane:TermCmd(jobArgs)
end

function init()
    -- config.TryBindKey("Ctrl-m", "lua:markdownPreview.markdownPreview", true)
    config.MakeCommand("glow", markdownPreview, config.NoComplete)
end

First, you need to install glow and wgo. Glow renders a markdown file, while wgo can check if a file changes and then it executes a command (it happens in an infinite loop).

These commands should work in the command-line:

$ glow file.md
$ wgo -file=file.md clear :: glow file.md

When you execute the second line (wgo), edit file.md and check if the preview is updated. It should be.

My plugin adds the command

> glow

It opens a vertical split and shows the preview via glow. However, the problem is that when you change the .md file, the preview is not updated and I don't know why. wgo is running but it doesn't work correctly in the splitted buffer.

Update (Nov. 2, 2023):

I also tried this alternative:

    local wgo = string.format('wgo -file=%s clear :: glow %s', buf.Path, buf.Path)
    local jobArgs = { "bash", "-c", wgo }

    --open terminal
    tmpBufferpane:TermCmd(jobArgs)

It's the same. The .md file is rendered just once and when I edit and save the file, it's not refreshed.

jabbalaci commented 11 months ago

Until someone comes up with a fix, I have a workaround solution, see here: https://github.com/jabbalaci/micro-plugins/tree/main/markdownPreview

I also copy it here:

Problem

You edit a markdown (.md) file and you want to preview it in a splitted pane.

Solution

This is how I do it:

Open a terminal in a vertical pane. I have a shortcut for that:

"Ctrl-t": "command:vsplit,command:term",

Issue this command:

$ preview FileName.md

Where FileName.md is the file you're editing in the other pane.

preview is a simple shell script:

#!/usr/bin/env bash

if [ $# -eq 0 ]; then
    echo "Provide the name of a markdown (.md) file"
    exit 1
fi

wgo -file=$1 clear :: glow $1

You need the following:

The markdown file will be rendered in the right pane and wgo is waiting for file changes. Go back to the left pane and edit the .md file. When you save it, the preview will be updated.

Limitations

The preview is not synced with the scroll position. And you cannot scroll up in the preview pane.

It is best suited for short markdown files that fit in one screen.

fakerybakery commented 11 months ago

Wow! Thanks! Is there a way to sync scroll positions?

dei-layborer commented 11 months ago

However, the problem is that when you change the .md file, the preview is not updated and I don't know why.

Easy :) ... the issue is that wgo looks for changes to the files in the directory. If you don't save the .md file, then there's no way for wgo to know that a refresh is needed. I tried your script, and it updates the preview every time the file is saved.

My suggestion is to get rid of wgo entirely.

Instead, you have a couple of options. One is to automatically update the preview at a regular interval. micro exposes Go's time package, which is a lot more capable in this regard than Lua. You could use a ticker, which is designed to do basically this. However, it uses channels, so may be harder to implement. (See here for how gopher-lua handles these.)

An alternative is to use time.Sleep, which simply pauses execution of its thread for whatever duration. I think this would be easier to implement, but you'll want to double-check a couple things:

  1. Does it lock micro entirely? I don't know how micro is structured thread-wise, so you'd have to experiment with this.
  2. Would it keep the plugin function (i.e. markdownPreview(bp)) running even after the pane is closed?

Another possibility, instead of doing a time-based method, is some other way of triggering the Lua function. For example, micro/buffer has the onRune(rune) callback, which triggers every time a rune is inserted. So you could have an initial command to create the pane, and then use onRune to refresh it.

There may be a way to instead use micro's built-in check for when a file is modified, but I couldn't find where that is, so YMMV.