Skip to content

Commit

Permalink
feat: initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
folke committed May 17, 2021
0 parents commit be5b6b6
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 0 deletions.
68 changes: 68 additions & 0 deletions lua/zen/config.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
local util = require("zen.util")
local M = {}

--- @type ZenOptions
M.options = {}

--- @class ZenOptions
local defaults = {
zindex = 40, -- zindex of the zen window. Should be less than 50, which is the float default
window = {
backdrop = 0.95, -- shade the backdrop of the zen window. Set to 1 to keep the same as Normal
-- height and width can be:
-- * an asbolute number of cells when > 1
-- * a percentage of the width / height of the editor when <= 1
width = 120, -- width of the zen window
height = 1, -- height of the zen window
-- by default, no options are changed in for the zen window
-- uncomment any of the options below, or add other vim.wo options you want to apply
options = {
-- signcolumn = "no", -- disable signcolumn
-- number = false, -- disable number column
-- relativenumber = false, -- disable relative numbers
-- cursorline = false, -- disable cursorline
-- cursorcolumn = false, -- disable cursor column
-- foldcolumn = "0", -- disable fold column
-- list = false, -- disable whitespace characters
},
},
plugins = {
gitsigns = true, -- disables git signs
tmux = true, -- disables the tmux statusline
-- this will change the font size on kitty when in zen mode
-- to make this work, you need to set the following kitty options:
-- - allow_remote_control socket-only
-- - listen_on unix:/tmp/kitty
kitty = {
enabled = false,
font = "+4", -- font size increment
},
},
-- callback where you can add custom code when the zen window opens
on_open = function(_win)
end,
-- callback where you can add custom code when the zen window closes
on_close = function()
end,
}

function M.setup(options)
M.options = vim.tbl_deep_extend("force", {}, defaults, options or {})
local normal = util.get_hl("Normal")
if normal and normal.background then
local bg = util.darken(normal.background, M.options.window.backdrop)
vim.cmd(("highlight ZenBg guibg=%s guifg=%s"):format(bg, bg))
end
for plugin, plugin_opts in pairs(M.options.plugins) do
if type(plugin_opts) == "boolean" then
M.options.plugins[plugin] = { enabled = plugin_opts }
end
if M.options.plugins[plugin].enabled == nil then
M.options.plugins[plugin].enabled = true
end
end
end

M.setup()

return M
17 changes: 17 additions & 0 deletions lua/zen/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
local view = require("zen.view")
local config = require("zen.config")

local M = {}

M.setup = config.setup
M.toggle = view.toggle
M.open = view.open
M.close = view.close

function M.reset()
M.close()
require("plenary.reload").reload_module("zen")
require("zen").toggle()
end

return M
48 changes: 48 additions & 0 deletions lua/zen/plugins.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
local M = {}

function M.gitsigns(state, disable)
local gs = require("gitsigns")
local config = gs._get_config()
if disable then
state.signcolumn = config.signcolumn
state.numhl = config.numhl
state.linehl = config.linehl
config.signcolumn = false
config.numhl = false
config.linehl = false
else
config.signcolumn = state.signcolumn
config.numhl = state.numhl
config.linehl = state.linehl
end
gs.refresh()
end

-- changes the kitty font size
-- it's a bit glitchy, but it works
function M.kitty(state, disable, opts)
if not vim.fn.executable("kitty") then
return
end
local cmd = "kitty @ --to %s set-font-size %s"
local socket = vim.fn.expand("$KITTY_LISTEN_ON")
if disable then
vim.fn.system(cmd:format(socket, opts.font))
else
vim.fn.system(cmd:format(socket, "0"))
end
vim.cmd([[redraw]])
end

function M.tmux(state, disable, opts)
if not vim.env.TMUX then
return
end
if disable then
vim.fn.system([[tmux set -g status off]])
else
vim.fn.system([[tmux set -g status on]])
end
end

return M
48 changes: 48 additions & 0 deletions lua/zen/util.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
local M = {}

function M.get_hl(name)
local ok, hl = pcall(vim.api.nvim_get_hl_by_name, name, true)
if not ok then
return
end
for _, key in pairs({ "foreground", "background", "special" }) do
if hl[key] then
hl[key] = string.format("#%06x", hl[key])
end
end
return hl
end

function M.hex2rgb(hex)
hex = hex:gsub("#", "")
return tonumber("0x" .. hex:sub(1, 2)), tonumber("0x" .. hex:sub(3, 4)), tonumber("0x" .. hex:sub(5, 6))
end

function M.rgb2hex(r, g, b)
return string.format("#%02x%02x%02x", r, g, b)
end

function M.darken(hex, amount)
local r, g, b = M.hex2rgb(hex)
return M.rgb2hex(r * amount, g * amount, b * amount)
end

function M.is_dark(hex)
local r, g, b = M.hex2rgb(hex)
local lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255
return lum <= 0.5
end

function M.log(msg, hl)
vim.api.nvim_echo({ { "Todo: ", hl }, { msg } }, true, {})
end

function M.warn(msg)
M.log(msg, "WarningMsg")
end

function M.error(msg)
M.log(msg, "ErrorMsg")
end

return M
211 changes: 211 additions & 0 deletions lua/zen/view.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
local config = require("zen.config")
local plugins = require("zen.plugins")
local M = {}

M.bg_win = nil
M.bg_buf = nil
M.win = nil
--- @type ZenOptions
M.opts = nil
M.state = {}
M.closed = false

function M.is_open()
return M.win and vim.api.nvim_win_is_valid(M.win)
end

function M.plugins_on_open()
for name, opts in pairs(M.opts.plugins) do
if opts and opts.enabled then
local plugin = plugins[name]
M.state[name] = {}
pcall(plugin, M.state[name], true, opts)
end
end
end

function M.plugins_on_close()
for name, opts in pairs(M.opts.plugins) do
if opts and opts.enabled then
local plugin = plugins[name]
pcall(plugin, M.state[name], false, opts)
end
end
end

function M.close()
pcall(vim.cmd, [[autocmd! Zen]])
pcall(vim.cmd, [[augroup! Zen]])
if M.win and vim.api.nvim_win_is_valid(M.win) then
vim.api.nvim_win_close(M.win, { force = true })
M.win = nil
end
if M.bg_win and vim.api.nvim_win_is_valid(M.bg_win) then
vim.api.nvim_win_close(M.bg_win, { force = true })
M.bg_win = nil
end
if M.bg_buf and vim.api.nvim_buf_is_valid(M.bg_buf) then
vim.api.nvim_buf_delete(M.bg_buf, { force = true })
M.bg_buf = nil
end
if M.opts then
M.plugins_on_close()
M.opts.on_close()
M.opts = nil
end
end

function M.open(opts)
if not M.is_open() then
-- close any possible remnants from a previous session
-- shouldn't happen, but just in case
M.close()
M.create(opts)
end
end

function M.toggle(opts)
if M.is_open() then
M.close()
else
M.open(opts)
end
end

function M.round(num)
return math.floor(num + 0.5)
end

--- @param opts ZenOptions
function M.layout(opts)
local width = vim.o.columns
if opts.window.width > 1 then
width = opts.window.width
else
width = width * opts.window.width
end
width = math.min(width, vim.o.columns)

local height = vim.o.lines
if opts.window.height > 1 then
height = opts.window.height
else
height = height * opts.window.height
end
height = math.min(height, vim.o.lines)

return {
width = M.round(width),
height = M.round(height),
col = M.round((vim.o.columns - width) / 2),
row = M.round((vim.o.lines - height) / 2),
}
end

-- adjusts col/row if window was resized
function M.fix_layout(win_resized)
if M.is_open() then
if win_resized then
vim.api.nvim_win_set_config(M.bg_win, { width = vim.o.columns, height = vim.o.lines })
end
local height = vim.api.nvim_win_get_height(M.win)
local width = vim.api.nvim_win_get_width(M.win)
local col = M.round((vim.o.columns - width) / 2)
local row = M.round((vim.o.lines - height) / 2)
local cfg = vim.api.nvim_win_get_config(M.win)
-- HACK: col is an array?
local wcol = type(cfg.col) == "number" and cfg.col or cfg.col[false]
local wrow = type(cfg.row) == "number" and cfg.row or cfg.row[false]
if wrow ~= row or wcol ~= col then
vim.api.nvim_win_set_config(M.win, { col = col, row = row, relative = "editor" })
end
end
end

--- @param opts ZenOptions
function M.create(opts)
opts = vim.tbl_deep_extend("force", {}, config.options, opts or {})
M.opts = opts
M.state = {}

M.bg_buf = vim.api.nvim_create_buf(false, true)
M.bg_win = vim.api.nvim_open_win(M.bg_buf, false, {
relative = "editor",
width = vim.o.columns,
height = vim.o.lines,
focusable = false,
row = 0,
col = 0,
style = "minimal",
zindex = opts.zindex - 10,
})
M.fix_hl(M.bg_win, "ZenBg")

local win_opts = vim.tbl_extend("keep", {
relative = "editor",
zindex = opts.zindex,
}, M.layout(opts))

local buf = vim.api.nvim_get_current_buf()
M.win = vim.api.nvim_open_win(buf, true, win_opts)
vim.cmd([[norm! zz]])
M.fix_hl(M.win)

for k, v in pairs(opts.window.options or {}) do
vim.api.nvim_win_set_option(M.win, k, v)
end

M.plugins_on_open()
if type(opts.on_open) == "function" then
opts.on_open(M.win)
end

-- fix layout since some plugins might have altered the window
M.fix_layout()

-- TODO: listen for WinNew and BufEnter. When a new window, or bufenter in a new window, close zen mode
-- unless it's in a float
-- TODO: when the cursor leaves the window, we close zen mode, or prevent leaving the window
local augroup = [[
augroup Zen
autocmd!
autocmd WinClosed %d ++once ++nested lua require("zen.view").close()
autocmd WinEnter * lua require("zen.view").on_win_enter()
autocmd CursorMoved * lua require("zen.view").fix_layout()
autocmd VimResized * lua require("zen.view").fix_layout(true)
autocmd CursorHold * lua require("zen.view").fix_layout()
autocmd BufWinEnter %d lua require("zen.view").on_buf_win_enter()
augroup end]]

vim.api.nvim_exec(augroup:format(M.win, M.win), false)
end

function M.fix_hl(win, normal)
normal = normal or "Normal"
vim.api.nvim_win_set_option(win, "winhighlight", "NormalFloat:" .. normal)
vim.api.nvim_win_set_option(win, "fcs", "eob: ")
end

function M.is_float(win)
local opts = vim.api.nvim_win_get_config(win)
return opts and opts.relative and opts.relative ~= ""
end

function M.on_buf_win_enter()
-- M.fix_hl(M.win)
end

function M.on_win_enter()
local win = vim.api.nvim_get_current_win()
if win ~= M.win and not M.is_float(win) then
-- HACK: when returning from a float window, vim initially enters the parent window.
-- give 10ms to get back to the zen window before closing
vim.defer_fn(function()
if vim.api.nvim_get_current_win() ~= M.win then
M.close()
end
end, 10)
end
end

return M
1 change: 1 addition & 0 deletions selene.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
std="lua51+vim"
Loading

0 comments on commit be5b6b6

Please sign in to comment.