diff --git a/lua/package-info/config.lua b/lua/package-info/config.lua index 3558ee6..afff2fe 100644 --- a/lua/package-info/config.lua +++ b/lua/package-info/config.lua @@ -1,13 +1,15 @@ --- FILE DESCRIPTION: User passed config options +-- DESCRIPTION: sets up the user given config, and plugin config local constants = require("package-info.constants") -local globals = require("package-info.globals") ---------------------------------------------------------------------------- ----------------------------------- HELPERS --------------------------------- +---------------------------------- MODULE ---------------------------------- ---------------------------------------------------------------------------- -local DEFAULT_OPTIONS = { +local M = {} + +--- Default options +M.options = { colors = { up_to_date = "#3C4048", outdated = "#d19a66", @@ -20,32 +22,31 @@ local DEFAULT_OPTIONS = { }, }, autostart = true, + + __highlight_params = { + fg = "guifg", + }, } -local highlight_param = "guifg" -if not vim.o.termguicolors then - highlight_param = "ctermfg" - DEFAULT_OPTIONS.colors = { - up_to_date = "237", - outdated = "173", - } -end -local register_highlight_group = function(group, color) - vim.cmd("highlight " .. group .. " " .. highlight_param .. "=" .. color) -end +M.__namespace = { + id = "", + register = function() + M.__namespace.id = vim.api.nvim_create_namespace("package-ui") + end, +} + +M.__state = { + displayed = M.options.autostart or false, +} -local register_colorscheme_autocmd = function() - vim.cmd([[ - augroup PackageInfoHighlight - autocmd! - autocmd ColorScheme * lua require('package-info.config').register_highlight_groups() - augroup END - ]]) +--- Clone options and replace empty ones with default ones +M.__register_user_options = function(user_options) + return vim.tbl_deep_extend("force", {}, M.options, user_options or {}) end --- Register autocommand for auto-starting plugin -local register_autostart = function(should_autostart) - if should_autostart then +--- Register autocommand for auto-starting plugin +M.__register_autostart = function() + if M.options.autostart then vim.api.nvim_exec( [[augroup PackageUI autocmd! @@ -56,27 +57,46 @@ local register_autostart = function(should_autostart) end end --- Clone options and replace empty ones with default ones -local register_user_options = function(options) - return vim.tbl_deep_extend("force", {}, DEFAULT_OPTIONS, options or {}) -end +--- If terminal doesn't support true color, fallback to 256 config +M.__register_256color_support = function() + if not vim.o.termguicolors then + vim.cmd([[ + augroup PackageUIHighlight + autocmd! + autocmd ColorScheme * lua require('package-info.config').__register_highlight_groups() + augroup END + ]]) ----------------------------------------------------------------------------- ----------------------------------- MODULE ---------------------------------- ----------------------------------------------------------------------------- + M.options.colors = { + up_to_date = "237", + outdated = "173", + } -local M = {} -M.register_highlight_groups = function() - register_highlight_group(constants.HIGHLIGHT_GROUPS.outdated, M.options.colors.outdated) - register_highlight_group(constants.HIGHLIGHT_GROUPS.up_to_date, M.options.colors.up_to_date) + M.options.__highlight_params.fg = "ctermfg" + end end -M.setup = function(options) - M.options = register_user_options(options) +--- Register given highlight group +-- @param group - highlight group +-- @param color - color to use with the highlight group +M.__register_highlight_group = function(group, color) + vim.cmd("highlight " .. group .. " " .. M.options.__highlight_params.fg .. "=" .. color) +end - register_autostart(M.options.autostart) - register_colorscheme_autocmd() - M.register_highlight_groups() - globals.namespace.register() +--- Register all highlight groups +M.__register_highlight_groups = function() + M.__register_highlight_group(constants.HIGHLIGHT_GROUPS.outdated, M.options.colors.outdated) + M.__register_highlight_group(constants.HIGHLIGHT_GROUPS.up_to_date, M.options.colors.up_to_date) end + +--- Take all user options and setup the config +-- @param user_options - all the options user can provide in the plugin config // See M.options for defaults +M.setup = function(user_options) + M.__register_user_options(user_options) + M.__register_autostart() + M.__register_256color_support() + M.__register_highlight_groups() + M.__namespace.register() +end + return M diff --git a/lua/package-info/constants.lua b/lua/package-info/constants.lua index b7607db..43f34f0 100644 --- a/lua/package-info/constants.lua +++ b/lua/package-info/constants.lua @@ -7,4 +7,9 @@ M.HIGHLIGHT_GROUPS = { up_to_date = "PackageInfoUpToDateVersion", } +M.PACKAGE_MANAGERS = { + yarn = "yarn", + npm = "npm", +} + return M diff --git a/lua/package-info/globals.lua b/lua/package-info/globals.lua deleted file mode 100644 index 1baa153..0000000 --- a/lua/package-info/globals.lua +++ /dev/null @@ -1,18 +0,0 @@ --- FILE DESCRIPTION: Plugin global state - ----------------------------------------------------------------------------- ----------------------------------- MODULE ---------------------------------- ----------------------------------------------------------------------------- - -local M = {} - -M.namespace = { - id = "", - register = function() - M.namespace.id = vim.api.nvim_create_namespace("package-ui") - end, -} - -M.buffer = {} - -return M diff --git a/lua/package-info/init.lua b/lua/package-info/init.lua index be40ecd..b8d58e5 100644 --- a/lua/package-info/init.lua +++ b/lua/package-info/init.lua @@ -2,7 +2,6 @@ local config = require("package-info.config") -local manager_module = require("package-info.modules.manager") local core_module = require("package-info.modules.core") local M = {} @@ -20,7 +19,11 @@ M.hide = function() end M.delete = function() - manager_module.delete() + core_module.delete() +end + +M.update = function() + core_module.update() end return M diff --git a/lua/package-info/modules/core.lua b/lua/package-info/modules/core.lua new file mode 100644 index 0000000..8f21cdc --- /dev/null +++ b/lua/package-info/modules/core.lua @@ -0,0 +1,226 @@ +-- DESCRIPTION: holds logic for buffer parsing and displaying virtual text + +local json_parser = require("package-info.libs.json_parser") + +local config = require("package-info.config") +local constants = require("package-info.constants") +local ui = require("package-info.ui") + +local M = {} + +--- Checks if the currently opened file is package.json and has content +M.__is_valid_package_json = function() + local current_buffer_name = vim.api.nvim_buf_get_name(0) + local is_package_json = string.match(current_buffer_name, "package.json$") + local buffer_size = vim.fn.getfsize(current_buffer_name) + + return is_package_json and buffer_size > 0 +end + +--- Parse current buffer and return its value +M.__get_buffer_content = function() + local buffer_raw_value = vim.api.nvim_buf_get_lines(0, 0, -1, false) + local buffer_string_value = table.concat(buffer_raw_value) + local buffer_json_value = json_parser.decode(buffer_string_value) + + return { + raw = buffer_raw_value, + string = buffer_string_value, + json = buffer_json_value, + } +end + +--- Fetches outdated npm dependencies for the project +-- @param callback - function that will receive outdated packages in JSON format +M.__get_outdated_dependencies = function(callback) + local value = "" + + vim.fn.jobstart("npm outdated --json", { + on_stdout = function(_, stdout) + value = value .. table.concat(stdout) + + if table.concat(stdout) == "" then + local json_value = json_parser.decode(value) + + callback(json_value) + end + end, + }) +end + +--- Gets dependencies from the package.json in JSON format +M.__get_dependencies = function() + local buffer_content = M.__get_buffer_content() + + local dev_dependencies = buffer_content.json["devDependencies"] or {} + local prod_dependencies = buffer_content.json["dependencies"] or {} + + return { + prod = prod_dependencies, + dev = dev_dependencies, + } +end + +--- Gets the package name from the given buffer line +-- @param line - string representing a buffer line +M.__get_package_name_from_line = function(line) + return string.match(line, [["(.-)"]]) +end + +--- Checks if the package exists in either dev or prod dependency list +-- @param package_name - string +M.__is_valid_package = function(package_name) + local dependencies = M.__get_dependencies() + + local is_dev_dependency = dependencies.dev[package_name] + local is_prod_dependency = dependencies.prod[package_name] + + if is_dev_dependency or is_prod_dependency then + return true + end + + return false +end + +--- Maps each dependency to its location in the buffer +M.__get_dependency_positions = function() + local buffer_content = M.__get_buffer_content() + + local dependency_positions = {} + + for buffer_line_number, buffer_line_content in pairs(buffer_content.raw) do + local package_name = M.__get_package_name_from_line(buffer_line_content) + local is_valid = M.__is_valid_package(package_name) + + if is_valid then + dependency_positions[package_name] = buffer_line_number - 1 + end + end + + return dependency_positions +end + +--- Gets metadata used for setting the version virtual text +-- @param current_package_version - string +-- @param outdated_dependencies - json/table +-- @param package_name - string +M.__get_package_metadata = function(current_package_version, outdated_dependencies, package_name) + local package_metadata = { + group = constants.HIGHLIGHT_GROUPS.up_to_date, + icon = config.options.icons.style.up_to_date, + version = current_package_version, + } + + local is_outdated = outdated_dependencies[package_name] + + if is_outdated then + package_metadata = { + version = outdated_dependencies[package_name].latest, + group = constants.HIGHLIGHT_GROUPS.outdated, + icon = config.options.icons.style.outdated, + } + end + + if not config.options.icons.enable then + package_metadata.icon = "" + end + + return package_metadata +end + +--- Sets virtual text for `devDependencies` and `dependencies` +-- @param dependencies - json/table from dev or prod dependencies +-- @param outdated_dependencies - outdated project dependencies in JSON format +M.__set_virtual_text = function(dependencies, outdated_dependencies) + if not M.__is_valid_package_json() then + return + end + + local dependency_positions = M.__get_dependency_positions() + + for package_name, current_package_version in pairs(dependencies) do + local package_metadata = M.__get_package_metadata(current_package_version, outdated_dependencies, package_name) + + local virtual_text = package_metadata.icon .. package_metadata.version + local position = dependency_positions[package_name] + + vim.api.nvim_buf_set_extmark(0, config.__namespace.id, position, 0, { + virt_text = { { virtual_text, package_metadata.group } }, + virt_text_pos = "eol", + priority = 200, + }) + end +end + +M.show = function() + if not M.__is_valid_package_json() then + return + end + + local dependencies = M.__get_dependencies() + + M.__get_outdated_dependencies(function(outdated_dependencies_json) + M.__set_virtual_text(dependencies.dev, outdated_dependencies_json) + M.__set_virtual_text(dependencies.prod, outdated_dependencies_json) + + config.__state.displayed = true + end) +end + +M.hide = function() + vim.api.nvim_buf_clear_namespace(0, config.__namespace.id, 0, -1) + + config.__state.displayed = false +end + +M.delete = function() + local current_line = vim.fn.getline(".") + + local package_name = M.__get_package_name_from_line(current_line) + local is_valid = M.__is_valid_package(package_name) + + if not is_valid then + vim.api.nvim_echo({ { "No package under current line.", "WarningMsg" } }, {}, {}) + else + ui.display_menu({ + command = "yarn remove " .. package_name, + title = " Delete [" .. package_name .. "] Package ", + callback = function() + vim.api.nvim_echo({ { package_name .. " deleted successfully" } }, {}, {}) + vim.cmd(":e") + + if config.__state.displayed then + M.hide() + M.show() + end + end, + }) + end +end + +M.update = function() + local current_line = vim.fn.getline(".") + + local package_name = M.__get_package_name_from_line(current_line) + local is_valid = M.__is_valid_package(package_name) + + if not is_valid then + vim.api.nvim_echo({ { "No package under current line.", "WarningMsg" } }, {}, {}) + else + ui.display_menu({ + command = "yarn upgrade --latest " .. package_name, + title = " Update [" .. package_name .. "] Package ", + callback = function() + vim.api.nvim_echo({ { package_name .. " updated successfully" } }, {}, {}) + vim.cmd(":e") + + if config.__state.displayed then + M.hide() + M.show() + end + end, + }) + end +end + +return M diff --git a/lua/package-info/modules/core/hide.lua b/lua/package-info/modules/core/hide.lua deleted file mode 100644 index 4561c34..0000000 --- a/lua/package-info/modules/core/hide.lua +++ /dev/null @@ -1,12 +0,0 @@ --- FILE DESCRIPTION: Functionality for clearing package info virtual text - -local globals = require("package-info.globals") - ----------------------------------------------------------------------------- ------------------------------- RETURN FUNCTION ----------------------------- ----------------------------------------------------------------------------- - --- Contains functionality needed in order to clear package-info virtual text -return function() - vim.api.nvim_buf_clear_namespace(0, globals.namespace.id, 0, -1) -end diff --git a/lua/package-info/modules/core/init.lua b/lua/package-info/modules/core/init.lua deleted file mode 100644 index d9d0914..0000000 --- a/lua/package-info/modules/core/init.lua +++ /dev/null @@ -1,16 +0,0 @@ --- FILE DESCRIPTION: Core module entry point - -local hide = require("package-info.modules.core.hide") -local show = require("package-info.modules.core.show") - -local M = {} - -M.hide = function() - hide() -end - -M.show = function() - show() -end - -return M diff --git a/lua/package-info/modules/core/show.lua b/lua/package-info/modules/core/show.lua deleted file mode 100644 index f6e6ee1..0000000 --- a/lua/package-info/modules/core/show.lua +++ /dev/null @@ -1,110 +0,0 @@ --- FILE DESCRIPTION: Functionality related to showing latest package versions as virtual text - -local json_parser = require("package-info.libs.json_parser") - -local constants = require("package-info.constants") -local config = require("package-info.config") -local globals = require("package-info.globals") - -local utils = require("package-info.utils") - ----------------------------------------------------------------------------- ----------------------------------- HELPERS --------------------------------- ----------------------------------------------------------------------------- - --- Checks if the open buffer refers to a non-empty package.json file -local is_valid_package_json = function() - local current_buffer_name = vim.api.nvim_buf_get_name(0) - local is_package_json = string.match(current_buffer_name, "package.json$") - local buffer_size = vim.fn.getfsize(current_buffer_name) - - return is_package_json and buffer_size > 0 -end - --- Determine if package is outdated and return meta about it accordingly -local get_package_metadata = function(current_package_version, outdated_dependencies, package_name) - local package_metadata = { - group = constants.HIGHLIGHT_GROUPS.up_to_date, - icon = config.options.icons.style.up_to_date, - version = current_package_version, - } - - if outdated_dependencies[package_name] then - package_metadata = { - version = outdated_dependencies[package_name].latest, - group = constants.HIGHLIGHT_GROUPS.outdated, - icon = config.options.icons.style.outdated, - } - end - - if config.options.icons.enable == false then - package_metadata.icon = "" - end - - return package_metadata -end - -local set_virtual_text = function(dependencies, outdated_dependencies) - if not is_valid_package_json() then - return - end - - local dependency_positions = utils.buffer.get_dependency_positions() - - for package_name, current_package_version in pairs(dependencies) do - local package_metadata = get_package_metadata(current_package_version, outdated_dependencies, package_name) - - local virtual_text = package_metadata.icon .. package_metadata.version - local position = dependency_positions[package_name] - - vim.api.nvim_buf_set_extmark(0, globals.namespace.id, position, 0, { - virt_text = { { virtual_text, package_metadata.group } }, - virt_text_pos = "eol", - priority = 200, - }) - end -end - -local get_outdated_dependencies = function(callback) - local command = "npm outdated --json" - - -- https://github.com/vuki656/package-info.nvim/issues/19 - local done = false - - vim.fn.jobstart(command, { - on_stdout = function(_, stdout) - if done == false then - local string_value = table.concat(stdout) - local json_value = json_parser.decode(string_value) - - callback(json_value) - end - - done = true - end, - on_stderr = function(_, stderr) - if stderr[0] ~= nil then - vim.api.nvim_echo({ { "Package info retrieval failed.", "WarningMsg" } }, {}, {}) - end - end, - }) -end - ----------------------------------------------------------------------------- ------------------------------- RETURN FUNCTION ----------------------------- ----------------------------------------------------------------------------- - --- Contains functionality needed in order to set the virtual text -return function() - if not is_valid_package_json() then - return - end - - local dev_dependencies, prod_dependencies, peer_dependencies = utils.buffer.get_dependencies() - - get_outdated_dependencies(function(outdated_dependencies) - set_virtual_text(dev_dependencies, outdated_dependencies) - set_virtual_text(prod_dependencies, outdated_dependencies) - set_virtual_text(peer_dependencies, outdated_dependencies) - end) -end diff --git a/lua/package-info/modules/manager/delete.lua b/lua/package-info/modules/manager/delete.lua deleted file mode 100644 index 2d5fed0..0000000 --- a/lua/package-info/modules/manager/delete.lua +++ /dev/null @@ -1,76 +0,0 @@ --- FILE DESCRIPTION: Functionality related to deleting dependency on current line - -local Menu = require("nui.menu") - -local utils = require("package-info.utils") - ----------------------------------------------------------------------------- ----------------------------------- HELPERS --------------------------------- ----------------------------------------------------------------------------- - -local DELETE_ACTIONS = { - confirm = "Confirm", - cancel = "Cancel", -} - -local display_menu = function(package_name) - local menu = Menu({ - relative = "cursor", - border = { - style = "rounded", - highlight = "Normal", - text = { - top = " Delete [" .. package_name .. "] Package ", - top_align = "left", - }, - }, - position = { - row = 0, - col = 0, - }, - size = { - width = 50, - height = 2, - }, - highlight = "Normal:Normal", - focusable = true, - }, { - lines = { - Menu.item(DELETE_ACTIONS.confirm), - Menu.item(DELETE_ACTIONS.cancel), - }, - keymap = { - focus_next = { "j", "", "" }, - focus_prev = { "k", "", "" }, - close = { "", "" }, - submit = { "", "" }, - }, - on_submit = function(selected_action) - if selected_action.text == DELETE_ACTIONS.confirm then - vim.fn.jobstart("yarn remove " .. package_name, { - on_stdout = function() - vim.api.nvim_echo({ { package_name .. " deleted successfully" } }, {}, {}) - vim.cmd("e") - end, - }) - end - end, - }) - - menu:mount() -end - ----------------------------------------------------------------------------- ------------------------------- RETURN FUNCTION ----------------------------- ----------------------------------------------------------------------------- - -return function() - local current_line = vim.fn.getline(".") - local package_name = utils.buffer.get_package_from_line(current_line, true) - - if not package_name then - vim.api.nvim_echo({ { "No package under current line.", "WarningMsg" } }, {}, {}) - else - display_menu(package_name) - end -end diff --git a/lua/package-info/modules/manager/init.lua b/lua/package-info/modules/manager/init.lua deleted file mode 100644 index 6fde9bb..0000000 --- a/lua/package-info/modules/manager/init.lua +++ /dev/null @@ -1,9 +0,0 @@ -local delete = require("package-info.modules.manager.delete") - -local M = {} - -M.delete = function() - delete() -end - -return M diff --git a/lua/package-info/ui.lua b/lua/package-info/ui.lua new file mode 100644 index 0000000..d1fab94 --- /dev/null +++ b/lua/package-info/ui.lua @@ -0,0 +1,58 @@ +local Menu = require("nui.menu") + +local ACTIONS = { + confirm = "Confirm", + cancel = "Cancel", +} + +local M = {} + +M.display_menu = function(options) + local menu = Menu({ + relative = "cursor", + border = { + style = "rounded", + highlight = "Normal", + text = { + top = options.title, + top_align = "left", + }, + }, + position = { + row = 0, + col = 0, + }, + size = { + width = 50, + height = 2, + }, + highlight = "Normal:Normal", + focusable = true, + }, { + lines = { + Menu.item(ACTIONS.confirm), + Menu.item(ACTIONS.cancel), + }, + keymap = { + focus_next = { "j", "", "" }, + focus_prev = { "k", "", "" }, + close = { "", "" }, + submit = { "", "" }, + }, + on_submit = function(selected_action) + if selected_action.text == ACTIONS.confirm then + vim.fn.jobstart(options.command, { + on_stdout = function(_, stdout) + if table.concat(stdout) == "" then + options.callback() + end + end, + }) + end + end, + }) + + menu:mount() +end + +return M diff --git a/lua/package-info/utils/buffer.lua b/lua/package-info/utils/buffer.lua deleted file mode 100644 index 0daff07..0000000 --- a/lua/package-info/utils/buffer.lua +++ /dev/null @@ -1,101 +0,0 @@ --- FILE DESCRIPTION: Functionality related to buffer parsing - -local json_parser = require("package-info.libs.json_parser") - ----------------------------------------------------------------------------- ----------------------------------- MODULE ---------------------------------- ----------------------------------------------------------------------------- - -local M = { - json_value = nil, - raw_value = nil, -} - -M.__parse = function() - local buffer_content = vim.api.nvim_buf_get_lines(0, 0, -1, false) - local buffer_string_value = table.concat(buffer_content) - local buffer_json_value = json_parser.decode(buffer_string_value) - - M.json_value = buffer_json_value - M.raw_value = buffer_content -end - -M.validate_package = function(package_name) - local prod_dependencies, dev_dependencies, peer_dependencies = M.get_dependencies() - - local is_dev_dependency = dev_dependencies[package_name] - local is_prod_dependency = prod_dependencies[package_name] - local is_peer_dependency = peer_dependencies[package_name] - - if is_dev_dependency or is_prod_dependency or is_peer_dependency then - return true - end - - return false -end - -M.get_package_from_line = function(line, should_validate_prop) - local should_validate_package = should_validate_prop or false - - local package_name = string.match(line, [["(.-)"]]) - - if should_validate_package then - local is_package_valid = M.validate_package(package_name) - - if is_package_valid then - return package_name - else - return nil - end - end - - return package_name -end - -M.get_dependency_positions = function() - local buffer_content = M.get_raw() - - local dependency_positions = {} - - for buffer_line_number, buffer_line_content in pairs(buffer_content) do - local package_name = M.get_package_from_line(buffer_line_content, true) - - if package_name then - dependency_positions[package_name] = buffer_line_number - 1 - end - end - - return dependency_positions -end - -M.get_dependencies = function() - local json_value = M.get_json() - - local dev_dependencies = json_value["devDependencies"] or {} - local prod_dependencies = json_value["dependencies"] or {} - local peer_dependencies = json_value["peerDependencies"] or {} - - return prod_dependencies, dev_dependencies, peer_dependencies -end - -M.get_json = function() - if M.json_value then - return M.json_value - else - M.__parse() - end - - return M.json_value -end - -M.get_raw = function() - if M.raw_value then - return M.raw_value - else - M.__parse() - end - - return M.raw_value -end - -return M diff --git a/lua/package-info/utils/init.lua b/lua/package-info/utils/init.lua deleted file mode 100644 index fc33c67..0000000 --- a/lua/package-info/utils/init.lua +++ /dev/null @@ -1,7 +0,0 @@ -local buffer_utils = require("package-info.utils.buffer") - -local M = {} - -M.buffer = buffer_utils - -return M