From 2926bd9256e8e66912890800851ce9f8c9694557 Mon Sep 17 00:00:00 2001
From: kawre <kenterq@gmail.com>
Date: Sat, 26 Oct 2024 22:06:13 +0200
Subject: [PATCH 1/4] feat(config): question filename customization

---
 README.md                        | 21 +++++++++++++++++++++
 lua/leetcode-ui/question.lua     | 14 +++-----------
 lua/leetcode/config/langs.lua    |  4 ++--
 lua/leetcode/config/template.lua |  8 ++++++++
 4 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/README.md b/README.md
index d4541a00..16910fd2 100644
--- a/README.md
+++ b/README.md
@@ -147,6 +147,12 @@ To see full configuration types see [template.lua](./lua/leetcode/config/templat
         ["leave"] = {},
     },
 
+    ---@type lc.filename
+    filename = function(id, title, alias, extension)
+        local parts = alias and { id, title, alias, extension } or { id, title, extension }
+        return table.concat(parts, ".")
+    end,
+
     keys = {
         toggle = { "q" }, ---@type string|string[]
         confirm = { "<CR>" }, ---@type string|string[]
@@ -300,6 +306,21 @@ hooks = {
 },
 ```
 
+### filename
+
+Custom function to both create and read questions
+
+`alias` can be `nil` and is used for differentiating between `Python` and
+`Python3` languages, since they share the same file extension `.py`
+
+```lua
+---@type lc.filename
+filename = function(id, title, alias, extension)
+    local parts = alias and { id, title, alias, extension } or { id, title, extension }
+    return table.concat(parts, ".")
+end,
+```
+
 ### theme
 
 Override the [default theme](./lua/leetcode/theme/default.lua).
diff --git a/lua/leetcode-ui/question.lua b/lua/leetcode-ui/question.lua
index d9093734..b7901000 100644
--- a/lua/leetcode-ui/question.lua
+++ b/lua/leetcode-ui/question.lua
@@ -60,19 +60,11 @@ end
 ---@return string path, boolean existed
 function Question:path()
     local lang = utils.get_lang(self.lang)
-    local alt = lang.alt and ("." .. lang.alt) or ""
 
-    -- handle legacy file names first
-    local fn_legacy = --
-        ("%s.%s-%s.%s"):format(self.q.frontend_id, self.q.title_slug, lang.slug, lang.ft)
-    self.file = config.storage.home:joinpath(fn_legacy)
+    local id, title, alias, extension = self.q.frontend_id, self.q.title_slug, lang.alias, lang.ft
+    local filename = config.user.filename(id, title, alias, extension)
 
-    if self.file:exists() then
-        return self.file:absolute(), true
-    end
-
-    local fn = ("%s.%s%s.%s"):format(self.q.frontend_id, self.q.title_slug, alt, lang.ft)
-    self.file = config.storage.home:joinpath(fn)
+    self.file = config.storage.home:joinpath(filename)
     local existed = self.file:exists()
 
     if not existed then
diff --git a/lua/leetcode/config/langs.lua b/lua/leetcode/config/langs.lua
index c3db2f48..d6c21752 100644
--- a/lua/leetcode/config/langs.lua
+++ b/lua/leetcode/config/langs.lua
@@ -6,7 +6,7 @@
 ---@field hl? string
 ---@field comment string
 ---@field ft string
----@field alt? string
+---@field alias? string
 
 ---@type lc.language[]
 return {
@@ -33,7 +33,7 @@ return {
         color = "#306998",
         ft = "py",
         comment = "#",
-        alt = "python2",
+        alias = "python2",
     },
     {
         lang = "Python3",
diff --git a/lua/leetcode/config/template.lua b/lua/leetcode/config/template.lua
index 27590e0b..b6ea9434 100644
--- a/lua/leetcode/config/template.lua
+++ b/lua/leetcode/config/template.lua
@@ -38,6 +38,8 @@
 
 ---@alias lc.storage table<"cache"|"home", string>
 
+---@alias lc.filename fun(id: string, title: string, alias: string|nil, extension: string): string
+
 ---@class lc.UserConfig
 local M = {
     ---@type string
@@ -112,6 +114,12 @@ local M = {
         ["leave"] = {},
     },
 
+    ---@type lc.filename
+    filename = function(id, title, alias, extension)
+        local parts = alias and { id, title, alias, extension } or { id, title, extension }
+        return table.concat(parts, ".")
+    end,
+
     keys = {
         toggle = { "q" }, ---@type string|string[]
         confirm = { "<CR>" }, ---@type string|string[]

From 646bdbc668dc7b2a221abe84220f38b8a9f135e0 Mon Sep 17 00:00:00 2001
From: kawre <kenterq@gmail.com>
Date: Sat, 26 Oct 2024 23:00:43 +0200
Subject: [PATCH 2/4] chore(README): `filename` documentation

---
 README.md | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 16910fd2..98ed83dd 100644
--- a/README.md
+++ b/README.md
@@ -308,10 +308,14 @@ hooks = {
 
 ### filename
 
-Custom function to both create and read questions
+This function is used for both creating and finding existing questions
 
-`alias` can be `nil` and is used for differentiating between `Python` and
-`Python3` languages, since they share the same file extension `.py`
+`alias` is an optional parameter used for differentiating between `Python` and
+`Python3` problems, since they share the same file extension `.py`
+
+> [!WARNING]
+> Modifying this function will make questions with old filenames not
+> discoverable
 
 ```lua
 ---@type lc.filename

From 1a7b596ee15cffeceb1cb76b34066bbdd6dd80d9 Mon Sep 17 00:00:00 2001
From: kawre <kenterq@gmail.com>
Date: Sun, 27 Oct 2024 20:46:12 +0100
Subject: [PATCH 3/4] refactor: move to `question`

---
 README.md                        | 11 ++---------
 lua/leetcode-ui/question.lua     | 10 ++++++++--
 lua/leetcode/api/types.lua       |  2 +-
 lua/leetcode/config/template.lua |  9 +++------
 4 files changed, 14 insertions(+), 18 deletions(-)

diff --git a/README.md b/README.md
index 98ed83dd..7e855e8a 100644
--- a/README.md
+++ b/README.md
@@ -147,11 +147,8 @@ To see full configuration types see [template.lua](./lua/leetcode/config/templat
         ["leave"] = {},
     },
 
-    ---@type lc.filename
-    filename = function(id, title, alias, extension)
-        local parts = alias and { id, title, alias, extension } or { id, title, extension }
-        return table.concat(parts, ".")
-    end,
+    ---@type lc.filename|nil,
+    filename = nil,
 
     keys = {
         toggle = { "q" }, ---@type string|string[]
@@ -313,10 +310,6 @@ This function is used for both creating and finding existing questions
 `alias` is an optional parameter used for differentiating between `Python` and
 `Python3` problems, since they share the same file extension `.py`
 
-> [!WARNING]
-> Modifying this function will make questions with old filenames not
-> discoverable
-
 ```lua
 ---@type lc.filename
 filename = function(id, title, alias, extension)
diff --git a/lua/leetcode-ui/question.lua b/lua/leetcode-ui/question.lua
index b7901000..cf117d2c 100644
--- a/lua/leetcode-ui/question.lua
+++ b/lua/leetcode-ui/question.lua
@@ -61,8 +61,14 @@ end
 function Question:path()
     local lang = utils.get_lang(self.lang)
 
-    local id, title, alias, extension = self.q.frontend_id, self.q.title_slug, lang.alias, lang.ft
-    local filename = config.user.filename(id, title, alias, extension)
+    ---@type lc.filename
+    local generate_fn = config.user.filename
+        or function(id, title, alias, extension)
+            local parts = alias and { id, title, alias, extension } or { id, title, extension }
+            return table.concat(parts, ".")
+        end
+
+    local filename = generate_fn(self.q.frontend_id, self.q.title_slug, lang.alias, lang.ft)
 
     self.file = config.storage.home:joinpath(filename)
     local existed = self.file:exists()
diff --git a/lua/leetcode/api/types.lua b/lua/leetcode/api/types.lua
index 7cd2f84d..5fe43cc0 100644
--- a/lua/leetcode/api/types.lua
+++ b/lua/leetcode/api/types.lua
@@ -254,7 +254,7 @@
 
 ---@class lc.QuestionResponse
 ---@field id integer
----@field frontend_id integer
+---@field frontend_id string
 ---@field title string
 ---@field translated_title string
 ---@field title_slug string
diff --git a/lua/leetcode/config/template.lua b/lua/leetcode/config/template.lua
index b6ea9434..2b18b1db 100644
--- a/lua/leetcode/config/template.lua
+++ b/lua/leetcode/config/template.lua
@@ -38,7 +38,7 @@
 
 ---@alias lc.storage table<"cache"|"home", string>
 
----@alias lc.filename fun(id: string, title: string, alias: string|nil, extension: string): string
+---@alias lc.filename fun(id: string, title: string, alias?: string, extension: string): string
 
 ---@class lc.UserConfig
 local M = {
@@ -114,11 +114,8 @@ local M = {
         ["leave"] = {},
     },
 
-    ---@type lc.filename
-    filename = function(id, title, alias, extension)
-        local parts = alias and { id, title, alias, extension } or { id, title, extension }
-        return table.concat(parts, ".")
-    end,
+    ---@type lc.filename|nil,
+    filename = nil,
 
     keys = {
         toggle = { "q" }, ---@type string|string[]

From 3fd19b0ccddd2b5094ce20a8b34554a6e7f58000 Mon Sep 17 00:00:00 2001
From: kawre <kenterq@gmail.com>
Date: Sun, 27 Oct 2024 21:36:50 +0100
Subject: [PATCH 4/4] feat: `Question:filename`

---
 README.md                        |  4 +--
 lua/leetcode-ui/question.lua     | 34 +++++++++++++++++++-------
 lua/leetcode/config/template.lua |  2 +-
 lua/leetcode/logger/init.lua     | 42 +++++++++++++++++++-------------
 4 files changed, 53 insertions(+), 29 deletions(-)

diff --git a/README.md b/README.md
index 7e855e8a..01b87c9e 100644
--- a/README.md
+++ b/README.md
@@ -303,7 +303,7 @@ hooks = {
 },
 ```
 
-### filename
+### custom filename
 
 This function is used for both creating and finding existing questions
 
@@ -312,7 +312,7 @@ This function is used for both creating and finding existing questions
 
 ```lua
 ---@type lc.filename
-filename = function(id, title, alias, extension)
+custom_filename = function(id, title, alias, extension)
     local parts = alias and { id, title, alias, extension } or { id, title, extension }
     return table.concat(parts, ".")
 end,
diff --git a/lua/leetcode-ui/question.lua b/lua/leetcode-ui/question.lua
index cf117d2c..9bbf7a59 100644
--- a/lua/leetcode-ui/question.lua
+++ b/lua/leetcode-ui/question.lua
@@ -57,20 +57,36 @@ function Question:reset_lines()
     self:set_lines(new_lines)
 end
 
----@return string path, boolean existed
-function Question:path()
+function Question:filename()
     local lang = utils.get_lang(self.lang)
 
-    ---@type lc.filename
-    local generate_fn = config.user.filename
-        or function(id, title, alias, extension)
-            local parts = alias and { id, title, alias, extension } or { id, title, extension }
-            return table.concat(parts, ".")
+    local id, title, alias, extension = self.q.frontend_id, self.q.title_slug, lang.alias, lang.ft
+
+    local parts = alias and { id, title, alias, extension } or { id, title, extension }
+    local filename = table.concat(parts, ".")
+
+    if config.user.custom_filename then
+        local ok, cfn = pcall(config.user.custom_filename, id, title, alias, extension)
+
+        if ok then
+            return cfn
+        else
+            local err_msg = ("[opts.custom_filename]\n%s\nfalling back to `%s`"):format(
+                cfn,
+                filename
+            )
+            log.error(err_msg, true)
         end
+    end
+
+    return filename
+end
 
-    local filename = generate_fn(self.q.frontend_id, self.q.title_slug, lang.alias, lang.ft)
+---@return string path, boolean existed
+function Question:path()
+    local fn = self:filename()
 
-    self.file = config.storage.home:joinpath(filename)
+    self.file = config.storage.home:joinpath(fn)
     local existed = self.file:exists()
 
     if not existed then
diff --git a/lua/leetcode/config/template.lua b/lua/leetcode/config/template.lua
index 2b18b1db..c68aca0d 100644
--- a/lua/leetcode/config/template.lua
+++ b/lua/leetcode/config/template.lua
@@ -115,7 +115,7 @@ local M = {
     },
 
     ---@type lc.filename|nil,
-    filename = nil,
+    custom_filename = nil,
 
     keys = {
         toggle = { "q" }, ---@type string|string[]
diff --git a/lua/leetcode/logger/init.lua b/lua/leetcode/logger/init.lua
index a10c8332..f9617552 100644
--- a/lua/leetcode/logger/init.lua
+++ b/lua/leetcode/logger/init.lua
@@ -14,14 +14,10 @@ local function normalize(msg)
     return type(msg) == "string" and t(msg) or vim.inspect(msg)
 end
 
--- ---@private
--- ---@param msg any
--- ---@param lvl? integer
--- ---@return any
--- logger.log = vim.schedule_wrap(function(msg, lvl)
--- end)
-
-function logger.log(msg, lvl)
+---@param msg any
+---@param lvl? integer
+---@param schedule? boolean
+function logger.log(msg, lvl, schedule)
     if not config.user.logging then
         return
     end
@@ -34,27 +30,39 @@ function logger.log(msg, lvl)
         msg = debug.traceback(msg .. "\n")
     end
 
-    vim.notify(msg, lvl, { title = title })
+    local function notify_send()
+        vim.notify(msg, lvl, { title = title })
+    end
+
+    if schedule then
+        vim.schedule(notify_send)
+    else
+        notify_send()
+    end
 end
 
 ---@param msg any
-logger.info = function(msg)
-    logger.log(msg)
+---@param schedule? boolean
+logger.info = function(msg, schedule)
+    logger.log(msg, nil, schedule)
 end
 
 ---@param msg any
-logger.warn = function(msg)
-    logger.log(msg, lvls.WARN)
+---@param schedule? boolean
+logger.warn = function(msg, schedule)
+    logger.log(msg, lvls.WARN, schedule)
 end
 
 ---@param msg any
-logger.error = function(msg)
-    logger.log(msg, lvls.ERROR)
+---@param schedule? boolean
+logger.error = function(msg, schedule)
+    logger.log(msg, lvls.ERROR, schedule)
     logger.debug(msg)
 end
 
 ---@param err lc.err
-logger.err = function(err)
+---@param schedule? boolean
+logger.err = function(err, schedule)
     if not err then
         return logger.error("error")
     end
@@ -62,7 +70,7 @@ logger.err = function(err)
     local msg = err.msg or ""
     local lvl = err.lvl or lvls.ERROR
 
-    logger.log(msg, lvl)
+    logger.log(msg, lvl, schedule)
 end
 
 ---@param msg any
