Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ plugin, i.e., `v1.1.*` instead of `v1.*`.
- Scroll preview to the current cursor position. This can be used in combination with
`:TypstPreviewNoFollowCursor` so that the preview only scroll to the current cursor position
when you want it to.
- `:TypstPreviewExport [output]` or `require 'typst-preview'.export_pdf()`:
- Export the current typst file to PDF using `typst compile`.
- If `output` is omitted, it exports next to the main file with a `.pdf` extension.

## ⚙️ Configuration

Expand Down Expand Up @@ -117,6 +120,10 @@ require 'typst-preview'.setup {
-- Whether the preview will follow the cursor in the source file
follow_cursor = true,

-- Typst binary used for PDF export
-- Example: typst_bin = '/usr/local/bin/typst'
typst_bin = 'typst',

-- Provide the path to binaries for dependencies.
-- Setting this will skip the download of the binary by the plugin.
-- Warning: Be aware that your version might be older than the one
Expand All @@ -130,6 +137,10 @@ require 'typst-preview'.setup {
-- For example, extra_args = { "--input=ver=draft", "--ignore-system-fonts" }
extra_args = nil,

-- Extra arguments (or nil) to be passed to `typst compile` for export.
-- For example, export_args = { "--input=ver=draft" }
export_args = nil,

-- This function will be called to determine the root of the typst project
get_root = function(path_of_main_file)
local root = os.getenv 'TYPST_ROOT'
Expand Down
150 changes: 137 additions & 13 deletions lua/typst-preview/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,137 @@ local servers = require 'typst-preview.servers'

local M = {}

local function get_path(action)
local path = utils.get_buf_path(0)
if path == '' then
local message = action or 'preview'
utils.notify(
'Can not ' .. message .. ' an unsaved buffer.',
vim.log.levels.ERROR
)
return nil
end

return config.opts.get_main_file(path)
end

local function normalize_export_args(path, output)
local args = {
'compile',
'--root',
config.opts.get_root(path),
}

if config.opts.export_args ~= nil then
local extra = config.opts.export_args
if type(extra) == 'function' then
local ok, res = pcall(extra, path, output)
if ok and res ~= nil then
if type(res) == 'table' then
for _, v in ipairs(res) do
table.insert(args, v)
end
elseif type(res) == 'string' then
table.insert(args, res)
end
end
elseif type(extra) == 'table' then
for _, v in ipairs(extra) do
table.insert(args, v)
end
else
error 'config.opts.export_args must be a table or function'
end
end

table.insert(args, path)
if output ~= nil and output ~= '' then
table.insert(args, output)
end

return args
end

---Scroll all preview to cursor position.
function M.sync_with_cursor()
for _, ser in pairs(servers.get_all()) do
servers.sync_with_cursor(ser)
end
end

---Export the current typst file to PDF using typst compile.
---@param output string|nil
function M.export_pdf(output)
local path = get_path 'export'
if path == nil then
return
end

if output == nil or output == '' then
output = vim.fn.fnamemodify(path, ':r') .. '.pdf'
end

local typst_bin = config.opts.typst_bin or 'typst'
if vim.fn.executable(typst_bin) == 0 then
utils.notify(
'typst binary not found. Set typst_bin in setup or install typst.',
vim.log.levels.ERROR
)
return
end

local args = normalize_export_args(path, output)
local stdout = assert(vim.uv.new_pipe())
local stderr = assert(vim.uv.new_pipe())
local out_chunks = {}
local err_chunks = {}

local handle, _ = vim.uv.spawn(typst_bin, {
args = args,
stdio = { nil, stdout, stderr },
}, function(code)
stdout:close()
stderr:close()
if handle then
handle:close()
end

local err_msg = table.concat(err_chunks, '')
if code == 0 then
utils.notify('Exported PDF to ' .. output, vim.log.levels.INFO)
else
if err_msg == '' then
err_msg = table.concat(out_chunks, '')
end
utils.notify(
'typst compile failed (exit ' .. tostring(code) .. '): ' .. err_msg,
vim.log.levels.ERROR
)
end
end)

if not handle then
utils.notify('Failed to spawn typst process.', vim.log.levels.ERROR)
return
end

stdout:read_start(function(err, data)
if err then
utils.debug('typst stdout error: ' .. err)
elseif data then
table.insert(out_chunks, data)
end
end)

stderr:read_start(function(err, data)
if err then
utils.debug('typst stderr error: ' .. err)
elseif data then
table.insert(err_chunks, data)
end
end)
end

---Create user commands
function M.create_commands()
local function preview_off()
Expand All @@ -25,16 +149,6 @@ function M.create_commands()
end
end

local function get_path()
local path = utils.get_buf_path(0)
if path == '' then
utils.notify('Can not preview an unsaved buffer.', vim.log.levels.ERROR)
return nil
else
return config.opts.get_main_file(path)
end
end

---@param mode mode?
local function preview_on(mode)
-- check if binaries are available and tell them to fetch first
Expand All @@ -51,7 +165,7 @@ function M.create_commands()
end
end

local path = get_path()
local path = get_path 'preview'
if path == nil then
return
end
Expand Down Expand Up @@ -89,7 +203,7 @@ function M.create_commands()
end
else
assert(#opts.fargs == 0)
local path = get_path()
local path = get_path 'preview'
if path == nil then
return
end
Expand All @@ -108,7 +222,7 @@ function M.create_commands()
})
vim.api.nvim_create_user_command('TypstPreviewStop', preview_off, {})
vim.api.nvim_create_user_command('TypstPreviewToggle', function()
local path = get_path()
local path = get_path 'preview'
if path == nil then
return
end
Expand All @@ -132,6 +246,16 @@ function M.create_commands()
vim.api.nvim_create_user_command('TypstPreviewSyncCursor', function()
M.sync_with_cursor()
end, {})

vim.api.nvim_create_user_command('TypstPreviewExport', function(opts)
local output
if #opts.fargs == 1 then
output = opts.fargs[1]
end
M.export_pdf(output)
end, {
nargs = '?',
})
end

return M
8 changes: 6 additions & 2 deletions lua/typst-preview/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@ local M = {
host = '127.0.0.1',
invert_colors = 'never',
follow_cursor = true,
typst_bin = 'typst', -- let user directly point to binary in case of different PATH
dependencies_bin = {
['tinymist'] = nil,
['websocat'] = nil,
},
extra_args = nil,
export_args = nil,
get_root = function(path_of_main_file)
local env_root = os.getenv 'TYPST_ROOT'
if env_root then
return env_root
end

-- Use project markers to pick a root that still allows parent imports
local main_dir = vim.fs.dirname(vim.fn.fnamemodify(path_of_main_file, ':p'))
local found = vim.fs.find(root_markers, { path = main_dir, upward = true })
local main_dir =
vim.fs.dirname(vim.fn.fnamemodify(path_of_main_file, ':p'))
local found =
vim.fs.find(root_markers, { path = main_dir, upward = true })
if #found > 0 then
return vim.fs.dirname(found[1])
end
Expand Down
1 change: 1 addition & 0 deletions lua/typst-preview/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local M = {
set_follow_cursor = config.set_follow_cursor,
get_follow_cursor = config.get_follow_cursor,
sync_with_cursor = commands.sync_with_cursor,
export_pdf = commands.export_pdf,
update = function()
fetch.fetch(false)
end,
Expand Down