From 945994a6ee68df097b0fae872ed6ca8528c112fd Mon Sep 17 00:00:00 2001 From: Ajay Mamtora <45173937+Ajaymamtora@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:50:31 +0000 Subject: [PATCH] Merge in settings.lua --- lua/neoconf/editor.lua | 123 +++++++++++++++++++++++++++++++++++++ lua/neoconf/init.lua | 117 +++++++++++++++++++++++++++++++++++ lua/neoconf/utils/json.lua | 97 +++++++++++++++++++++++++++++ lua/neoconf/view.lua | 65 +++++++++++++++++++- 4 files changed, 400 insertions(+), 2 deletions(-) create mode 100644 lua/neoconf/editor.lua create mode 100644 lua/neoconf/utils/json.lua diff --git a/lua/neoconf/editor.lua b/lua/neoconf/editor.lua new file mode 100644 index 0000000..b52ba6f --- /dev/null +++ b/lua/neoconf/editor.lua @@ -0,0 +1,123 @@ +local Config = require("neoconf.config") +local Json = require("neoconf.utils.json") +local Settings = require("neoconf.settings") +local Util = require("neoconf.util") + +local M = {} + +-- Cache directory for temporary files +M.TMP_DIR = vim.fn.stdpath("cache") .. "/.neoconf_tmp" + +---@class EditorOptions +---@field on_save fun()|nil Called after saving settings +---@field on_close fun()|nil Called when closing the editor + +---Create a temporary JSON file +---@return string filepath +function M.create_temp_file() + vim.fn.mkdir(M.TMP_DIR, "p") + local random_name = vim.fn.system("openssl rand -hex 8"):gsub("\n", "") + return M.TMP_DIR .. "/" .. random_name .. ".json" +end + +---Open settings in a new buffer +---@param settings table The settings to edit +---@param opts EditorOptions|nil +---@return number bufnr +function M.open(settings, opts) + opts = vim.tbl_deep_extend("force", { + on_save = function() end, + on_close = function() end, + }, opts or {}) + + local temp_file = M.create_temp_file() + local formatted = Json.encode(settings, { sort = true }) + + -- Write formatted content to temp file + local file = io.open(temp_file, "w") + if file then + file:write(formatted) + file:close() + else + error("Failed to write temporary file: " .. temp_file) + end + + -- Open in new tab + vim.cmd("tabnew " .. temp_file) + local buf = vim.api.nvim_get_current_buf() + + -- Mark as temporary buffer + vim.api.nvim_buf_set_var(buf, "is_temp_settings_buffer", true) + vim.api.nvim_buf_set_var(buf, "temp_file_path", temp_file) + + -- Setup buffer + vim.api.nvim_buf_set_option(buf, "filetype", "json") + vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe") + + -- Save keymap + vim.keymap.set("n", "s", function() + M.save_current_buffer() + opts.on_save() + end, { buffer = buf, noremap = true, silent = true }) + + -- Clean up on buffer close + vim.api.nvim_create_autocmd("BufUnload", { + buffer = buf, + once = true, + callback = function() + os.remove(temp_file) + opts.on_close() + end, + }) + + return buf +end + +---Save the current buffer settings +function M.save_current_buffer() + local buf = vim.api.nvim_get_current_buf() + + -- Verify this is a settings buffer + if not pcall(vim.api.nvim_buf_get_var, buf, "is_temp_settings_buffer") then + Util.error("This is not a temporary settings buffer") + return + end + + -- Get current content + local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) + local content = table.concat(lines, "\n") + + -- Validate JSON + local ok, decoded = pcall(vim.json.decode, content) + if not ok then + Util.error("Invalid JSON. Please check your changes") + return + end + + -- Write to local config + local success = Settings.write_local(decoded) + if success then + Util.info("Settings saved successfully") + Settings.refresh() + vim.api.nvim_buf_delete(buf, { force = true }) + end +end + +---Close all temporary settings buffers +function M.close_all() + local closed = 0 + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + local is_temp = pcall(vim.api.nvim_buf_get_var, buf, "is_temp_settings_buffer") + if is_temp then + local file = vim.api.nvim_buf_get_name(buf) + os.remove(file) + vim.api.nvim_buf_delete(buf, { force = true }) + closed = closed + 1 + end + end + if closed > 0 then + Util.info(string.format("Closed %d temporary settings buffer(s)", closed)) + end +end + +return M diff --git a/lua/neoconf/init.lua b/lua/neoconf/init.lua index 0227765..6f0cffd 100644 --- a/lua/neoconf/init.lua +++ b/lua/neoconf/init.lua @@ -1,3 +1,6 @@ +local Settings = require("neoconf.settings") +local Util = require("neoconf.util") + local M = {} function M.setup(opts) @@ -19,4 +22,118 @@ function M.get(key, defaults, opts) return require("neoconf.workspace").get(opts).settings:get(key, { defaults = defaults }) end +---Toggle a boolean value at a specific settings path +---@param path string The dot-separated path to the setting +---@return boolean|nil new_value The new value after toggling +---@return string|nil error +function M.toggle(path) + local settings = Settings.get_local(vim.uv.cwd()) + local current = settings:get(path) + + -- If value doesn't exist, start with false + if type(current) ~= "boolean" then + current = false + end + + -- Create new settings with toggled value + local new_settings = {} + local parts = vim.split(path, ".", { plain = true }) + local node = new_settings + for i = 1, #parts - 1 do + node[parts[i]] = {} + node = node[parts[i]] + end + node[parts[#parts]] = not current + + -- Write to local settings + local success = Settings.write_local(new_settings) + if not success then + return nil, "Failed to write settings" + end + + Settings.refresh() + return not current +end + +---Toggle a string value in a table at a specific settings path +---@param str string The string to toggle +---@param path string The dot-separated path to the array +---@return boolean|nil success +---@return string|nil error +function M.toggle_string_in_table(str, path) + local settings = Settings.get_local(vim.uv.cwd()) + local current = settings:get(path) + + if type(current) ~= "table" then + current = {} + end + + -- Toggle string in array + local found = false + for i, v in ipairs(current) do + if v == str then + table.remove(current, i) + found = true + break + end + end + + if not found then + table.insert(current, str) + end + + -- Create new settings + local new_settings = {} + local parts = vim.split(path, ".", { plain = true }) + local node = new_settings + for i = 1, #parts - 1 do + node[parts[i]] = {} + node = node[parts[i]] + end + node[parts[#parts]] = current + + -- Write to local settings + local success = Settings.write_local(new_settings) + if not success then + return nil, "Failed to write settings" + end + + Settings.refresh() + return true +end + +---Helper function to toggle LSP inlay hints +---@return boolean|nil new_state +function M.toggle_inlay_hints() + local new_state = M.toggle("lsp.inlay_hint") + if new_state ~= nil then + vim.lsp.inlay_hint.enable(0, new_state) + Util.info("Inlay hints " .. (new_state and "enabled" or "disabled")) + end + return new_state +end + +---Helper function to toggle autoformatting +---@return boolean|nil new_state +function M.toggle_autoformat() + local new_state = M.toggle("autoformat") + if new_state ~= nil then + Util.info("Autoformat " .. (new_state and "enabled" or "disabled")) + end + return new_state +end + +---Print the current state of a property +---@param property_name string The name to display in the message +---@param path string|nil The settings path (defaults to property_name) +function M.print_property_state(property_name, path) + local settings = Settings.get_local(vim.uv.cwd()) + local value = settings:get(path or property_name) + if value ~= nil then + Util.info(string.format("'%s' is currently set to: %s", property_name, tostring(value))) + else + Util.warn(string.format("'%s' is not set", property_name)) + end +end + return M diff --git a/lua/neoconf/utils/json.lua b/lua/neoconf/utils/json.lua new file mode 100644 index 0000000..5abde5f --- /dev/null +++ b/lua/neoconf/utils/json.lua @@ -0,0 +1,97 @@ +local uv = vim.uv or vim.loop + +local M = {} + +---@class JsonEncodeOptions +---@field sort boolean Sort object keys +---@field format boolean Format JSON output + +---Encode a Lua table to JSON +---@param value any The value to encode +---@param opts JsonEncodeOptions|nil +---@return string +function M.encode(value, opts) + opts = vim.tbl_extend("force", { sort = false, format = true }, opts or {}) + + -- Basic JSON encode + local ok, json = pcall(vim.json.encode, value) + if not ok then + error("Failed to encode JSON: " .. json) + end + + -- Format with jq if requested + if opts.format or opts.sort then + local args = { "jq" } + if opts.sort then + table.insert(args, "--sort-keys") + end + table.insert(args, ".") + + local result = vim.fn.system(args, json) + if vim.v.shell_error == 0 then + return result + end + -- Fallback to unformatted JSON if jq fails + return json + end + + return json +end + +---Write JSON to a file +---@param filepath string +---@param content any +---@param opts JsonEncodeOptions|nil +---@return boolean success, string? error +function M.write(filepath, content, opts) + -- Encode content + local ok, data = pcall(M.encode, content, opts) + if not ok then + return false, "Failed to encode JSON" + end + + -- Write to file + local fd = uv.fs_open(filepath, "w", 438) -- 0666 octal + if not fd then + return false, "Could not open file for writing" + end + + local success = pcall(function() + uv.fs_write(fd, data, 0) + uv.fs_close(fd) + end) + + if not success then + return false, "Failed to write file" + end + + return true +end + +---Read JSON from a file +---@param filepath string +---@return table|nil content +---@return string|nil error +function M.read(filepath) + local fd = uv.fs_open(filepath, "r", 438) + if not fd then + return nil, "Could not open file" + end + + local stat = uv.fs_fstat(fd) + local data = uv.fs_read(fd, stat.size, 0) + uv.fs_close(fd) + + if not data then + return nil, "Could not read file" + end + + local ok, content = pcall(vim.json.decode, data) + if not ok then + return nil, "Failed to decode JSON" + end + + return content +end + +return M diff --git a/lua/neoconf/view.lua b/lua/neoconf/view.lua index 6e835ac..40d8104 100644 --- a/lua/neoconf/view.lua +++ b/lua/neoconf/view.lua @@ -1,3 +1,5 @@ +local Editor = require("neoconf.editor") +local Settings = require("neoconf.settings") local Util = require("neoconf.util") local M = {} @@ -69,7 +71,7 @@ function M.show_lsp_settings() if Util.exists(item.file) then local line = "* " .. vim.fn.fnamemodify(item.file, ":~") if item.is_global then - line = line .. "  " + line = line .. " " end table.insert(content, line) end @@ -88,7 +90,7 @@ function M.show_settings() if Util.exists(item.file) then local line = "* " .. vim.fn.fnamemodify(item.file, ":~") if item.is_global then - line = line .. "  " + line = line .. " " end table.insert(content, line) end @@ -100,4 +102,63 @@ function M.show_settings() M.show(table.concat(content, "\n")) end +-- Display LSP client settings +function M.show_client_settings(client) + local client_settings = client.config.settings + local neoconf_settings = require("neoconf").get() + + if next(client_settings) ~= nil or next(neoconf_settings) ~= nil then + local merged = vim.tbl_deep_extend("force", {}, neoconf_settings, client_settings) + Editor.open(merged, { + on_save = function() + Settings.refresh() + end, + }) + else + Util.warn("No settings found for " .. client.name .. " or in neoconf") + end +end + +-- Choose and display LSP client settings +function M.choose_client() + local clients = vim.lsp.get_clients() + + if #clients == 0 then + M.show_local_settings() + else + local choices = { + { name = "Local Settings (No LSP merge)", action = M.show_local_settings }, + } + for _, client in ipairs(clients) do + table.insert(choices, { + name = client.name, + action = function() + M.show_client_settings(client) + end, + }) + end + + vim.ui.select(choices, { + prompt = "Choose settings to modify:", + format_item = function(choice) + return choice.name + end, + }, function(choice) + if choice then + choice.action() + end + end) + end +end + +-- Show local settings +function M.show_local_settings() + local settings = Settings.get_local(vim.uv.cwd()):get() + Editor.open(settings or {}, { + on_save = function() + Settings.refresh() + end, + }) +end + return M