From 20ff5fa218b4a27194fee0b3d023e92f797cd34d Mon Sep 17 00:00:00 2001
From: Folke Lemaitre <folke.lemaitre@gmail.com>
Date: Tue, 29 Nov 2022 12:02:25 +0100
Subject: [PATCH] feat: added profiler view

---
 README.md                  |  6 ++--
 lua/lazy/core/config.lua   |  2 +-
 lua/lazy/core/handler.lua  | 16 ++++------
 lua/lazy/core/loader.lua   |  4 +--
 lua/lazy/core/util.lua     | 10 +++---
 lua/lazy/util.lua          | 26 ----------------
 lua/lazy/view/commands.lua |  3 ++
 lua/lazy/view/init.lua     | 13 ++++----
 lua/lazy/view/render.lua   | 62 +++++++++++++++++++++++++++++++++-----
 9 files changed, 81 insertions(+), 61 deletions(-)

diff --git a/README.md b/README.md
index 2f93154..a39da2d 100644
--- a/README.md
+++ b/README.md
@@ -25,9 +25,9 @@
 
 - [ ] health checks: check merge conflicts async
 - [ ] defaults for git log
-- [ ] view keybindings for update/clean/...
-- [ ] add profiler to view
-- [ ] add buttons for actions
+- [x] view keybindings for update/clean/...
+- [x] add profiler to view
+- [x] add buttons for actions
 - [x] show time taken for op in view
 - [ ] package meta index (package.lua cache for all packages)
 - [ ] migrate from Packer
diff --git a/lua/lazy/core/config.lua b/lua/lazy/core/config.lua
index 3103c4e..2b3930b 100644
--- a/lua/lazy/core/config.lua
+++ b/lua/lazy/core/config.lua
@@ -17,7 +17,7 @@ M.defaults = {
   view = {
     icons = {
       start = "",
-      plugin = "",
+      plugin = " ",
       source = " ",
       config = "",
       event = "",
diff --git a/lua/lazy/core/handler.lua b/lua/lazy/core/handler.lua
index 1b393a8..b8f5682 100644
--- a/lua/lazy/core/handler.lua
+++ b/lua/lazy/core/handler.lua
@@ -53,7 +53,7 @@ function M.handlers.event(grouped)
         once = true,
         pattern = pattern,
         callback = function()
-          Util.track("event: " .. (_event == "User" and pattern or event))
+          Util.track({ event = event })
           Loader.load(plugins, { event = event })
           Util.track()
         end,
@@ -67,7 +67,7 @@ function M.handlers.keys(grouped)
     ---@cast keys string
     vim.keymap.set("n", keys, function()
       vim.keymap.del("n", keys)
-      Util.track("keys: " .. keys)
+      Util.track({ keys = keys })
       Loader.load(plugins, { keys = keys })
       vim.api.nvim_input(keys)
       Util.track()
@@ -84,7 +84,7 @@ function M.handlers.ft(grouped)
       pattern = ft,
       group = group,
       callback = function()
-        Util.track("filetype: " .. ft)
+        Util.track({ ft = ft })
         Loader.load(plugins, { ft = ft })
         Util.track()
       end,
@@ -95,13 +95,9 @@ end
 function M.handlers.cmd(grouped)
   for cmd, plugins in pairs(grouped) do
     ---@cast cmd string
-    local function _load(complete)
+    local function _load()
       vim.api.nvim_del_user_command(cmd)
-      if complete then
-        Util.track("cmd-complete: " .. cmd)
-      else
-        Util.track("cmd: " .. cmd)
-      end
+      Util.track({ cmd = cmd })
       Loader.load(plugins, { cmd = cmd })
       Util.track()
     end
@@ -120,7 +116,7 @@ function M.handlers.cmd(grouped)
       bang = true,
       nargs = "*",
       complete = function()
-        _load(true)
+        _load()
         -- HACK: trick Neovim to show the newly loaded command completion
         vim.api.nvim_input("<space><bs><tab>")
       end,
diff --git a/lua/lazy/core/loader.lua b/lua/lazy/core/loader.lua
index 49c9adb..0842239 100644
--- a/lua/lazy/core/loader.lua
+++ b/lua/lazy/core/loader.lua
@@ -22,7 +22,7 @@ function M.init_plugins()
   Util.track("plugin_init")
   for _, plugin in pairs(Config.plugins) do
     if plugin.init then
-      Util.track(plugin.name)
+      Util.track({ plugin = plugin.name, start = "init" })
       Util.try(plugin.init, "Failed to run `init` for **" .. plugin.name .. "**")
       Util.track()
     end
@@ -58,7 +58,7 @@ function M.load(plugins, reason, opts)
 
       table.insert(M.loading, plugin)
 
-      Util.track(plugin.name)
+      Util.track({ plugin = plugin.name, start = reason.start })
       M.packadd(plugin, opts and opts.load_start)
 
       if plugin.requires then
diff --git a/lua/lazy/core/util.lua b/lua/lazy/core/util.lua
index f2c5b71..8ead048 100644
--- a/lua/lazy/core/util.lua
+++ b/lua/lazy/core/util.lua
@@ -1,16 +1,16 @@
 local M = {}
 
----@alias LazyProfile {name: string, time: number, [number]:LazyProfile}
+---@alias LazyProfile {data: string|{[string]:string}, time: number, [number]:LazyProfile}
 
 ---@type LazyProfile[]
 M._profiles = { { name = "lazy" } }
 
----@param name string?
+---@param data (string|{[string]:string})?
 ---@param time number?
-function M.track(name, time)
-  if name then
+function M.track(data, time)
+  if data then
     local entry = {
-      name = name,
+      data = data,
       time = time or vim.loop.hrtime(),
     }
     table.insert(M._profiles[#M._profiles], entry)
diff --git a/lua/lazy/util.lua b/lua/lazy/util.lua
index 3ca2e8d..2356f63 100644
--- a/lua/lazy/util.lua
+++ b/lua/lazy/util.lua
@@ -52,32 +52,6 @@ function M.throttle(ms, fn)
   end
 end
 
-function M.profile()
-  local lines = { "# Profile" }
-
-  ---@param entry LazyProfile
-  local function _profile(entry, depth)
-    if entry.time < 0.5 then
-      -- Nothing
-    end
-
-    table.insert(
-      lines,
-      ("  "):rep(depth) .. "- " .. entry.name .. ": **" .. math.floor((entry.time or 0) / 1e6 * 100) / 100 .. "ms**"
-    )
-
-    for _, child in ipairs(entry) do
-      _profile(child, depth + 1)
-    end
-  end
-
-  for _, entry in ipairs(M._profiles[1]) do
-    _profile(entry, 1)
-  end
-
-  M.markdown(lines)
-end
-
 ---@return string?
 function M.head(file)
   local f = io.open(file)
diff --git a/lua/lazy/view/commands.lua b/lua/lazy/view/commands.lua
index 7c8d887..52e4626 100644
--- a/lua/lazy/view/commands.lua
+++ b/lua/lazy/view/commands.lua
@@ -36,6 +36,9 @@ M.commands = {
   help = function()
     View.show("help")
   end,
+  profile = function()
+    View.show("profile")
+  end,
   sync = function()
     Manage.clean({ interactive = true, clear = true, wait = true, mode = "sync" })
     Manage.update({ interactive = true })
diff --git a/lua/lazy/view/init.lua b/lua/lazy/view/init.lua
index 08095ee..c391235 100644
--- a/lua/lazy/view/init.lua
+++ b/lua/lazy/view/init.lua
@@ -12,7 +12,8 @@ M.modes = {
   { name = "check", key = "C", desc = "Check for updates and show the log (git fetch)" },
   { name = "log", key = "L", desc = "Show recent updates for all plugins" },
   { name = "restore", key = "R", desc = "Updates all plugins to the state in the lockfile" },
-  { name = "help", key = "g?", hide = true, desc = "Toggle this help page" },
+  { name = "profile", key = "P", desc = "Show detailed profiling", toggle = true },
+  { name = "help", key = "g?", hide = true, desc = "Toggle this help page", toggle = true },
 
   { plugin = true, name = "update", key = "u", desc = "Update this plugin. This will also update the lockfile" },
   {
@@ -36,11 +37,7 @@ function M.setup()
 end
 
 function M.show(mode)
-  if mode == "help" and M.mode == "help" then
-    M.mode = nil
-  else
-    M.mode = mode or M.mode
-  end
+  M.mode = mode or M.mode
   require("lazy.view.colors").setup()
 
   if M._buf and vim.api.nvim_buf_is_valid(M._buf) then
@@ -171,6 +168,10 @@ function M.show(mode)
           Commands.cmd(m.name, { plugin })
         end
       else
+        if M.mode == m.name and m.toggle then
+          M.mode = nil
+          return update()
+        end
         Commands.cmd(m.name)
       end
     end, { buffer = buf })
diff --git a/lua/lazy/view/render.lua b/lua/lazy/view/render.lua
index 9cddb59..96e39dc 100644
--- a/lua/lazy/view/render.lua
+++ b/lua/lazy/view/render.lua
@@ -60,6 +60,8 @@ function M:update()
 
   if mode == "help" then
     self:help()
+  elseif mode == "profile" then
+    self:profile()
   else
     for _, section in ipairs(Sections) do
       self:section(section)
@@ -109,7 +111,7 @@ function M:title()
   end
   self:nl()
 
-  if View.mode ~= "help" then
+  if View.mode ~= "help" and View.mode ~= "profile" then
     if self.progress.done < self.progress.total then
       self:append("Tasks: ", "LazyH2")
       self:append(self.progress.done .. "/" .. self.progress.total, "LazyMuted")
@@ -181,9 +183,14 @@ function M:diagnostic(diag)
   table.insert(self._diagnostics, diag)
 end
 
----@param plugin LazyPlugin
-function M:reason(plugin)
-  local reason = vim.deepcopy(plugin._.loaded or {})
+---@param reason? {[string]:string, time:number}
+---@param opts? {time_right?:boolean}
+function M:reason(reason, opts)
+  opts = opts or {}
+  if not reason then
+    return
+  end
+  reason = vim.deepcopy(reason)
   ---@type string?
   local source = reason.source
   if source then
@@ -207,12 +214,16 @@ function M:reason(plugin)
       end
     end
   end
-  self:append(" " .. math.floor((reason.time or 0) / 1e6 * 100) / 100 .. "ms", "Bold")
+  local time = " " .. math.floor((reason.time or 0) / 1e6 * 100) / 100 .. "ms"
+  if not opts.time_right then
+    self:append(time, "Bold")
+  end
   self:append(" ")
   -- self:append(" (", "Conceal")
   local first = true
   for key, value in pairs(reason) do
-    if key == "require" then
+    if type(key) == "number" then
+    elseif key == "require" then
       -- self:append("require", "@function.builtin")
       -- self:append("(", "@punctuation.bracket")
       -- self:append('"' .. value .. '"', "@string")
@@ -237,6 +248,9 @@ function M:reason(plugin)
       end
     end
   end
+  if opts.time_right then
+    self:append(time, "Bold")
+  end
   -- self:append(")", "Conceal")
 end
 
@@ -270,10 +284,14 @@ end
 
 ---@param plugin LazyPlugin
 function M:plugin(plugin)
-  self:append("  - ", "LazySpecial"):append(plugin.name)
+  if plugin._.loaded then
+    self:append("  ● ", "LazySpecial"):append(plugin.name)
+  else
+    self:append("  ○ ", "LazySpecial"):append(plugin.name)
+  end
   local plugin_start = self:row()
   if plugin._.loaded then
-    self:reason(plugin)
+    self:reason(plugin._.loaded)
   end
   self:diagnostics(plugin)
   self:nl()
@@ -377,4 +395,32 @@ function M:details(plugin)
   self:nl()
 end
 
+function M:profile()
+  self:append("Profile", "LazyH2"):nl():nl()
+  local symbols = {
+    "●",
+    "➜",
+    "★",
+    "‒",
+  }
+
+  ---@param entry LazyProfile
+  local function _profile(entry, depth)
+    local data = type(entry.data) == "string" and { source = entry.data } or entry.data
+    data.time = entry.time
+    local symbol = symbols[depth] or symbols[#symbols]
+    self:append(("  "):rep(depth)):append(" " .. symbol, "LazySpecial")
+    self:reason(data, { time_right = true })
+    self:nl()
+
+    for _, child in ipairs(entry) do
+      _profile(child, depth + 1)
+    end
+  end
+
+  for _, entry in ipairs(Util._profiles[1]) do
+    _profile(entry, 1)
+  end
+end
+
 return M