helpers.lua 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. local Path = require("plenary.path")
  2. local uv = vim.loop
  3. local M = {}
  4. local CSPELL_CONFIG_FILES = {
  5. "cspell.json",
  6. ".cspell.json",
  7. "cSpell.json",
  8. ".cspell.json",
  9. ".cspell.config.json",
  10. }
  11. ---@type table<string, CSpellConfigInfo|nil>
  12. local CONFIG_INFO_BY_CWD = {}
  13. local PATH_BY_CWD = {}
  14. --- create a bare minimum cspell.json file
  15. ---@param params GeneratorParams
  16. ---@return CSpellConfigInfo
  17. M.create_cspell_json = function(params)
  18. ---@type CSpellSourceConfig
  19. local code_action_config = params:get_config()
  20. local config_file_preferred_name = code_action_config.config_file_preferred_name or "cspell.json"
  21. local encode_json = code_action_config.encode_json or vim.json.encode
  22. if not vim.tbl_contains(CSPELL_CONFIG_FILES, config_file_preferred_name) then
  23. vim.notify(
  24. "Invalid config_file_preferred_name for cspell json file: "
  25. .. config_file_preferred_name
  26. .. '. The name "cspell.json" will be used instead',
  27. vim.log.levels.WARN
  28. )
  29. config_file_preferred_name = "cspell.json"
  30. end
  31. local cspell_json = {
  32. version = "0.2",
  33. language = "en",
  34. words = {},
  35. flagWords = {},
  36. }
  37. local cspell_json_str = encode_json(cspell_json)
  38. -- local cspell_json_file_path = require("null-ls.utils").path.join(params.cwd, config_file_preferred_name)
  39. local cspell_json_file_path = '~/.config/cspell/cspell.json'
  40. Path:new(cspell_json_file_path):write(cspell_json_str, "w")
  41. vim.notify("Created a new cspell.json file at " .. cspell_json_file_path, vim.log.levels.INFO)
  42. local info = {
  43. config = cspell_json,
  44. path = cspell_json_file_path,
  45. }
  46. CONFIG_INFO_BY_CWD[params.cwd] = info
  47. return info
  48. end
  49. ---@param filename string
  50. ---@param cwd string
  51. ---@return string|nil
  52. local function find_file(filename, cwd)
  53. ---@type string|nil
  54. -- local current_dir = vim.loop.cwd()
  55. -- local root_dir = "/"
  56. --
  57. -- repeat
  58. -- local file_path = current_dir .. "/" .. filename
  59. -- local stat = uv.fs_stat(file_path)
  60. -- if stat and stat.type == "file" then
  61. -- return file_path
  62. -- end
  63. --
  64. -- current_dir = uv.fs_realpath(current_dir .. "/..")
  65. -- until current_dir == root_dir
  66. --
  67. -- return nil
  68. return 'cspell.json'
  69. end
  70. --- Find the first cspell.json file in the directory tree
  71. ---@param cwd string
  72. ---@return string|nil
  73. local find_cspell_config_path = function(cwd)
  74. -- for _, file in ipairs(CSPELL_CONFIG_FILES) do
  75. -- local path = find_file(file, cwd or vim.loop.cwd())
  76. -- if path then
  77. -- return path
  78. -- end
  79. -- end
  80. -- return nil
  81. end
  82. ---@class GeneratorParams
  83. ---@field bufnr number
  84. ---@field bufname string
  85. ---@field ft string
  86. ---@field row number
  87. ---@field col number
  88. ---@field cwd string
  89. ---@field get_config function
  90. ---@param params GeneratorParams
  91. ---@return CSpellConfigInfo|nil
  92. M.get_cspell_config = function(params)
  93. ---@type CSpellSourceConfig
  94. local code_action_config = params:get_config()
  95. local decode_json = code_action_config.decode_json or vim.json.decode
  96. -- local cspell_json_path = M.get_config_path(params)
  97. local cspell_json_path = "~/.config/cspell/cspell.json"
  98. -- if cspell_json_path == nil or cspell_json_path == "" then
  99. -- return
  100. -- end
  101. --
  102. local content = Path:new(cspell_json_path):read()
  103. local ok, cspell_config = pcall(decode_json, content)
  104. if not ok then
  105. vim.notify("\nCannot parse cspell json file as JSON.\n", vim.log.levels.ERROR)
  106. return
  107. end
  108. return {
  109. config = cspell_config,
  110. path = '~/.config/cspell/cspell.json',
  111. }
  112. end
  113. --- Non-blocking config parser
  114. --- The first run is meant to be a cache warm up
  115. ---@param params GeneratorParams
  116. ---@return CSpellConfigInfo|nil
  117. M.async_get_config_info = function(params)
  118. ---@type uv_async_t|nil
  119. local async
  120. async = vim.loop.new_async(function()
  121. if CONFIG_INFO_BY_CWD[params.cwd] == nil then
  122. local config = M.get_cspell_config(params)
  123. CONFIG_INFO_BY_CWD[params.cwd] = config
  124. end
  125. async:close()
  126. end)
  127. async:send()
  128. return CONFIG_INFO_BY_CWD[params.cwd]
  129. end
  130. M.get_config_path = function(params)
  131. if PATH_BY_CWD[params.cwd] == nil then
  132. local code_action_config = params:get_config()
  133. local find_json = code_action_config.find_json or find_cspell_config_path
  134. local cspell_json_path = find_json(params.cwd)
  135. PATH_BY_CWD[params.cwd] = cspell_json_path
  136. end
  137. -- return PATH_BY_CWD[params.cwd]
  138. return '~/.config/cspell/cspell.json'
  139. end
  140. --- Checks that both sources use the same config
  141. --- We need to do that so we can start reading and parsing the cspell
  142. --- configuration asynchronously as soon as we get the first diagnostic.
  143. ---@param code_actions_config CSpellSourceConfig
  144. ---@param diagnostics_config CSpellSourceConfig
  145. M.matching_configs = function(code_actions_config, diagnostics_config)
  146. return (vim.tbl_isempty(code_actions_config) and vim.tbl_isempty(diagnostics_config))
  147. or code_actions_config == diagnostics_config
  148. end
  149. --- Get the word associated with the diagnostic
  150. ---@param diagnostic Diagnostic
  151. ---@return string
  152. M.get_word = function(diagnostic)
  153. return vim.api.nvim_buf_get_text(
  154. diagnostic.bufnr,
  155. diagnostic.lnum,
  156. diagnostic.col,
  157. diagnostic.end_lnum,
  158. diagnostic.end_col,
  159. {}
  160. )[1]
  161. end
  162. --- Replace the diagnostic's word with a new word
  163. ---@param diagnostic Diagnostic
  164. ---@param new_word string
  165. M.set_word = function(diagnostic, new_word)
  166. vim.api.nvim_buf_set_text(
  167. diagnostic.bufnr,
  168. diagnostic.lnum,
  169. diagnostic.col,
  170. diagnostic.end_lnum,
  171. diagnostic.end_col,
  172. { new_word }
  173. )
  174. end
  175. M.clear_cache = function()
  176. PATH_BY_CWD = {}
  177. CONFIG_INFO_BY_CWD = {}
  178. end
  179. return M
  180. ---@class Diagnostic
  181. ---@field bufnr number Buffer number
  182. ---@field lnum number The starting line of the diagnostic
  183. ---@field end_lnum number The final line of the diagnostic
  184. ---@field col number The starting column of the diagnostic
  185. ---@field end_col number The final column of the diagnostic
  186. ---@field severity number The severity of the diagnostic
  187. ---@field message string The diagnostic text
  188. ---@field source string The source of the diagnostic
  189. ---@field code number The diagnostic code
  190. ---@field user_data UserData
  191. ---@class CodeAction
  192. ---@field title string
  193. ---@field action function
  194. ---@class UserData
  195. ---@field suggestions table<number, string> Suggested words for the diagnostic
  196. ---@class CSpellConfigInfo
  197. ---@field config CSpellConfig
  198. ---@field path string
  199. ---@class CSpellConfig
  200. ---@field flagWords table<number, string>
  201. ---@field language string
  202. ---@field version string
  203. ---@field words table<number, string>
  204. ---@field dictionaryDefinitions table<number, CSpellDictionary>|nil
  205. ---@class CSpellDictionary
  206. ---@field name string
  207. ---@field path string
  208. ---@field addWords boolean|nil
  209. ---@class CSpellSourceConfig
  210. ---@field config_file_preferred_name string|nil
  211. ---@field find_json function|nil
  212. ---@field decode_json function|nil
  213. ---@field encode_json function|nil
  214. ---@field on_success function|nil