helpers.lua 7.6 KB

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