diff --git a/README.md b/README.md index 835eb75..7234e3e 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ Open any `.md` file in your IWE project and enjoy: | `:IWE init` | Initialize IWE project in current directory | | `:IWE lsp start/stop/restart/status/toggle_inlay_hints` | Control LSP server | | `:IWE telescope find_files/paths/roots/grep/backlinks/headers` | Launch Telescope pickers | +| `:IWE preview squash/export/export-headers/export-workspace` | Generate previews using IWE CLI | | `:IWE info` | Show plugin status and configuration | ## Telescope Integration @@ -103,6 +104,15 @@ The plugin provides LSP-powered Telescope pickers: - **`:IWE telescope backlinks`** - LSP references as backlinks (gr equivalent) - **`:IWE telescope headers`** - Document symbols as headers (go equivalent) +## Preview Integration + +The plugin provides preview generation using the IWE CLI: + +- **`:IWE preview squash`** - Generate squashed markdown preview (combines content with depth 3) +- **`:IWE preview export`** - Generate basic DOT graph as SVG (depth 2) +- **`:IWE preview export-headers`** - Generate DOT graph with headers as SVG (depth 2, includes headers) +- **`:IWE preview export-workspace`** - Generate full workspace graph as SVG (depth 1, all files) + ## Configuration The plugin works out of the box, but can be customized: @@ -119,6 +129,7 @@ require('iwe').setup({ enable_markdown_mappings = true, -- Core markdown editing keybindings enable_telescope_keybindings = false, -- Set to true to enable gf, gs, ga, g/, gr, go enable_lsp_keybindings = false, -- Set to true to enable IWE-specific LSP keybindings + enable_preview_keybindings = false, -- Set to true to enable preview keybindings leader = "", localleader = "" }, @@ -126,6 +137,11 @@ require('iwe').setup({ enabled = true, setup_config = true, load_extensions = { "ui-select", "emoji" } + }, + preview = { + output_dir = "~/tmp/preview", -- Directory for generated preview files + temp_dir = "/tmp", -- Directory for temporary files + auto_open = false -- Whether to automatically open generated previews } }) ``` @@ -186,6 +202,17 @@ Standard LSP actions are available when the LSP server is active: | `rn` | Rename symbol | | `f` | Format document | +### Preview Keybindings (when `enable_preview_keybindings = true`) + +IWE CLI preview generation in markdown files: + +| Key | Action | +|-----|--------| +| `ps` | Generate squash preview | +| `pe` | Generate export graph preview | +| `ph` | Generate export with headers preview | +| `pw` | Generate workspace preview | + ### Configuration Options ```lua @@ -194,6 +221,7 @@ require('iwe').setup({ enable_markdown_mappings = true, -- Enable markdown editing keybindings enable_telescope_keybindings = true, -- Enable telescope navigation keybindings enable_lsp_keybindings = true, -- Enable IWE-specific LSP keybindings + enable_preview_keybindings = true, -- Enable preview keybindings } }) ``` @@ -214,6 +242,12 @@ vim.keymap.set('n', 'e', '(iwe-lsp-extract-section)') vim.keymap.set('n', 'i', '(iwe-lsp-inline-reference)') vim.keymap.set('n', 'h', '(iwe-lsp-rewrite-list-section)') vim.keymap.set('n', 'l', '(iwe-lsp-rewrite-section-list)') + +-- Preview keybindings (when enable_preview_keybindings = true) +vim.keymap.set('n', 'ps', '(iwe-preview-squash)') +vim.keymap.set('n', 'pe', '(iwe-preview-export)') +vim.keymap.set('n', 'ph', '(iwe-preview-export-headers)') +vim.keymap.set('n', 'pw', '(iwe-preview-export-workspace)') ``` ## Requirements @@ -222,6 +256,10 @@ vim.keymap.set('n', 'l', '(iwe-lsp-rewrite-section-list)') - `iwes` LSP server in PATH - `nvim-telescope/telescope.nvim` +**For Preview Functionality:** +- `iwe` CLI in PATH (install from [iwe-org/iwe](https://github.com/iwe-org/iwe)) +- `neato` (Graphviz) for SVG generation + **Recommended:** - [`MeanderingProgrammer/markdown.nvim`](https://github.com/MeanderingProgrammer/markdown.nvim) - Enhanced markdown rendering that pairs perfectly with IWE's editing features @@ -235,6 +273,7 @@ Run `:checkhealth iwe` to diagnose any issues with: - Telescope integration - Project structure - Dependencies +- Preview functionality (IWE CLI and Graphviz) ## Contributing diff --git a/doc/iwe.txt b/doc/iwe.txt index b76b114..18c3375 100644 --- a/doc/iwe.txt +++ b/doc/iwe.txt @@ -108,8 +108,11 @@ COMMANDS *iwe-commands* Actions: start, stop, restart, status telescope {action} Control Telescope pickers - Actions: find_files, paths, roots, grep, backlinks, - headers, setup + Actions: find_files, paths, roots, grep, blockreferences, + backlinks, headers, setup + + preview {action} Generate previews using IWE CLI + Actions: squash, export, export-headers, export-workspace info Show plugin configuration and status @@ -138,7 +141,8 @@ Telescope keybindings (when enable_telescope_keybindings = true): `gs` (iwe-telescope-paths) `ga` (iwe-telescope-roots) `g/` (iwe-telescope-grep) - `gr` (iwe-telescope-backlinks) + `gr` (iwe-telescope-blockreferences) + `gR` (iwe-telescope-backlinks) `go` (iwe-telescope-headers) IWE LSP keybindings (when enable_lsp_keybindings = true): @@ -147,6 +151,12 @@ IWE LSP keybindings (when enable_lsp_keybindings = true): `h` (iwe-lsp-rewrite-list-section) `l` (iwe-lsp-rewrite-section-list) +Preview keybindings (when enable_preview_keybindings = true): + `ps` (iwe-preview-squash) + `pe` (iwe-preview-export) + `ph` (iwe-preview-export-headers) + `pw` (iwe-preview-export-workspace) + Default Neovim LSP keybindings (available when LSP is active): `gD` Go to declaration `gd` Go to definition @@ -172,12 +182,17 @@ Available Mappings:~ *(iwe-telescope-paths)* Workspace symbols - paths (gs equivalent) *(iwe-telescope-roots)* Namespace symbols - roots (ga equivalent) *(iwe-telescope-grep)* Live grep search (g/ equivalent) -*(iwe-telescope-backlinks)* LSP references - backlinks (gr equivalent) +*(iwe-telescope-blockreferences)* LSP references - blockreferences (gr equivalent) +*(iwe-telescope-backlinks)* LSP references - backlinks (gR equivalent) *(iwe-telescope-headers)* Document symbols - headers (go equivalent) *(iwe-lsp-extract-section)* Extract section (refactor) *(iwe-lsp-inline-reference)* Inline reference (refactor) *(iwe-lsp-rewrite-list-section)* Rewrite list section (refactor) *(iwe-lsp-rewrite-section-list)* Rewrite section list (refactor) +*(iwe-preview-squash)* Generate squash markdown preview +*(iwe-preview-export)* Generate export graph preview +*(iwe-preview-export-headers)* Generate export graph with headers preview +*(iwe-preview-export-workspace)* Generate workspace graph preview ================================================================================ FUNCTIONS *iwe-functions* @@ -212,12 +227,27 @@ require('iwe.telescope').pickers.roots() *iwe.telescope.roots* require('iwe.telescope').pickers.grep() *iwe.telescope.grep* Launch live grep picker (g/ equivalent). +require('iwe.telescope').pickers.blockreferences() *iwe.telescope.blockreferences* + Launch LSP references picker - blockreferences (gr equivalent). + require('iwe.telescope').pickers.backlinks() *iwe.telescope.backlinks* - Launch LSP references picker - backlinks (gr equivalent). + Launch LSP references picker - backlinks (gR equivalent). require('iwe.telescope').pickers.headers() *iwe.telescope.headers* Launch document symbols picker - headers (go equivalent). +require('iwe.preview').generate_squash_preview() *iwe.preview.squash* + Generate squashed markdown preview using iwe CLI. + +require('iwe.preview').generate_export_preview() *iwe.preview.export* + Generate basic DOT graph as SVG using iwe CLI. + +require('iwe.preview').generate_export_headers_preview() *iwe.preview.export-headers* + Generate DOT graph with headers as SVG using iwe CLI. + +require('iwe.preview').generate_export_workspace_preview() *iwe.preview.export-workspace* + Generate full workspace graph as SVG using iwe CLI. + ================================================================================ HEALTH CHECKS *iwe-health* @@ -233,6 +263,7 @@ The health check verifies: • Configuration path validity • Telescope availability and extensions • Optional dependencies +• Preview functionality (iwe CLI and Graphviz) • Current LSP server status ================================================================================ diff --git a/doc/tags b/doc/tags index 7117560..5323f2f 100644 --- a/doc/tags +++ b/doc/tags @@ -4,18 +4,15 @@ (iwe-insert-week) iwe.txt /*(iwe-insert-week)* (iwe-link-next) iwe.txt /*(iwe-link-next)* (iwe-link-prev) iwe.txt /*(iwe-link-prev)* -(iwe-lsp-code-action) iwe.txt /*(iwe-lsp-code-action)* -(iwe-lsp-declaration) iwe.txt /*(iwe-lsp-declaration)* -(iwe-lsp-definition) iwe.txt /*(iwe-lsp-definition)* -(iwe-lsp-diagnostic-next) iwe.txt /*(iwe-lsp-diagnostic-next)* -(iwe-lsp-diagnostic-prev) iwe.txt /*(iwe-lsp-diagnostic-prev)* (iwe-lsp-extract-section) iwe.txt /*(iwe-lsp-extract-section)* -(iwe-lsp-implementation) iwe.txt /*(iwe-lsp-implementation)* (iwe-lsp-inline-reference) iwe.txt /*(iwe-lsp-inline-reference)* -(iwe-lsp-rename) iwe.txt /*(iwe-lsp-rename)* (iwe-lsp-rewrite-list-section) iwe.txt /*(iwe-lsp-rewrite-list-section)* (iwe-lsp-rewrite-section-list) iwe.txt /*(iwe-lsp-rewrite-section-list)* (iwe-lsp-start) iwe.txt /*(iwe-lsp-start)* +(iwe-preview-export) iwe.txt /*(iwe-preview-export)* +(iwe-preview-export-headers) iwe.txt /*(iwe-preview-export-headers)* +(iwe-preview-export-workspace) iwe.txt /*(iwe-preview-export-workspace)* +(iwe-preview-squash) iwe.txt /*(iwe-preview-squash)* (iwe-telescope-backlinks) iwe.txt /*(iwe-telescope-backlinks)* (iwe-telescope-find-files) iwe.txt /*(iwe-telescope-find-files)* (iwe-telescope-grep) iwe.txt /*(iwe-telescope-grep)* @@ -38,6 +35,10 @@ iwe.get_project_root() iwe.txt /*iwe.get_project_root()* iwe.is_in_project() iwe.txt /*iwe.is_in_project()* iwe.lsp_available() iwe.txt /*iwe.lsp_available()* iwe.nvim iwe.txt /*iwe.nvim* +iwe.preview.export iwe.txt /*iwe.preview.export* +iwe.preview.export-headers iwe.txt /*iwe.preview.export-headers* +iwe.preview.export-workspace iwe.txt /*iwe.preview.export-workspace* +iwe.preview.squash iwe.txt /*iwe.preview.squash* iwe.setup() iwe.txt /*iwe.setup()* iwe.start_lsp() iwe.txt /*iwe.start_lsp()* iwe.telescope.backlinks iwe.txt /*iwe.telescope.backlinks* diff --git a/lua/iwe/commands.lua b/lua/iwe/commands.lua index 7b39e92..bd6279e 100644 --- a/lua/iwe/commands.lua +++ b/lua/iwe/commands.lua @@ -3,6 +3,7 @@ local M = {} local lsp = require('iwe.lsp') local telescope = require('iwe.telescope') +local preview = require('iwe.preview') ---Get completion for LSP commands @@ -17,6 +18,12 @@ local function complete_telescope_commands() return { 'find_files', 'paths', 'roots', 'grep', 'backlinks', 'headers', 'setup' } end +---Get completion for Preview commands +---@return string[] +local function complete_preview_commands() + return { 'squash', 'export', 'export-headers', 'export-workspace' } +end + ---Initialize IWE project in current directory local function init_iwe_project() local cwd = vim.fn.getcwd() @@ -112,6 +119,27 @@ local function handle_telescope_command(subcmd) end end +---Handle Preview subcommands +---@param subcmd string The subcommand (squash, export, export-headers, export-workspace) +local function handle_preview_command(subcmd) + if not preview.is_available() then + vim.notify("Preview not available - please install iwe CLI and neato (Graphviz)", vim.log.levels.ERROR) + return + end + + if subcmd == 'squash' then + preview.generate_squash_preview() + elseif subcmd == 'export' then + preview.generate_export_preview() + elseif subcmd == 'export-headers' then + preview.generate_export_headers_preview() + elseif subcmd == 'export-workspace' then + preview.generate_export_workspace_preview() + else + vim.notify(string.format("Unknown Preview command: %s", subcmd), vim.log.levels.ERROR) + end +end + ---Main IWE command handler ---@param opts table Command options from nvim_create_user_command local function iwe_command(opts) @@ -137,6 +165,12 @@ local function iwe_command(opts) return end handle_telescope_command(args[2]) + elseif subcmd == 'preview' or subcmd == 'prev' then + if #args < 2 then + vim.notify("Usage: IWE preview ", vim.log.levels.ERROR) + return + end + handle_preview_command(args[2]) elseif subcmd == 'init' then init_iwe_project() elseif subcmd == 'info' then @@ -163,6 +197,10 @@ local function iwe_command(opts) string.format(" Setup Config: %s", config.telescope.setup_config), string.format(" Extensions: %s", table.concat(config.telescope.load_extensions, ", ")), "", + "Preview Configuration:", + string.format(" Output Dir: %s", config.preview.output_dir), + string.format(" Auto Open: %s", config.preview.auto_open), + "", "Status:", string.format(" LSP Available: %s", lsp.is_available() and "Yes" or "No") } @@ -170,6 +208,7 @@ local function iwe_command(opts) local clients = vim.lsp.get_clients({ name = 'iwes' }) table.insert(lines, string.format(" LSP Running: %s", #clients > 0 and "Yes" or "No")) table.insert(lines, string.format(" Telescope Available: %s", telescope.is_available() and "Yes" or "No")) + table.insert(lines, string.format(" Preview Available: %s", preview.is_available() and "Yes" or "No")) -- Check for .iwe marker in current directory local iwe_root = vim.fs.root(0, {'.iwe'}) @@ -181,7 +220,7 @@ local function iwe_command(opts) end else vim.notify(string.format("Unknown IWE command: %s", subcmd), vim.log.levels.ERROR) - vim.notify("Available commands: lsp, telescope, init, info", vim.log.levels.INFO) + vim.notify("Available commands: lsp, telescope, preview, init, info", vim.log.levels.INFO) end end @@ -196,7 +235,7 @@ local function complete_iwe_command(arg_lead, cmd_line, _) -- If we're completing the first argument after IWE if arg_count == 1 then - local subcommands = { 'lsp', 'telescope', 'tel', 'init', 'info' } + local subcommands = { 'lsp', 'telescope', 'tel', 'preview', 'prev', 'init', 'info' } return vim.tbl_filter(function(cmd) return cmd:find('^' .. vim.pesc(arg_lead)) end, subcommands) @@ -209,6 +248,8 @@ local function complete_iwe_command(arg_lead, cmd_line, _) return complete_lsp_commands() elseif subcmd == 'telescope' or subcmd == 'tel' then return complete_telescope_commands() + elseif subcmd == 'preview' or subcmd == 'prev' then + return complete_preview_commands() end end diff --git a/lua/iwe/config.lua b/lua/iwe/config.lua index cae0fe1..80a5372 100644 --- a/lua/iwe/config.lua +++ b/lua/iwe/config.lua @@ -2,6 +2,7 @@ ---@field lsp IWE.Config.LSP LSP server configuration ---@field mappings IWE.Config.Mappings Key mapping configuration ---@field telescope IWE.Config.Telescope Telescope integration configuration +---@field preview IWE.Config.Preview Preview generation configuration ---@class IWE.Config.LSP @@ -15,6 +16,7 @@ ---@field enable_markdown_mappings boolean Whether to enable core markdown editing key mappings ---@field enable_telescope_keybindings boolean Whether to enable telescope keybindings (gf, gs, ga, etc.) ---@field enable_lsp_keybindings boolean Whether to enable IWE-specific LSP keybindings (e, i, etc.) +---@field enable_preview_keybindings boolean Whether to enable preview keybindings (ps, pe, etc.) ---@field leader string Leader key for mappings ---@field localleader string Local leader key for mappings @@ -23,6 +25,11 @@ ---@field setup_config boolean Whether to setup Telescope config automatically ---@field load_extensions string[] Extensions to load automatically +---@class IWE.Config.Preview +---@field output_dir string Directory for generated preview files +---@field temp_dir string Directory for temporary files during preview generation +---@field auto_open boolean Whether to automatically open generated previews + local M = {} ---@type IWE.Config @@ -38,6 +45,7 @@ M.defaults = { enable_markdown_mappings = true, enable_telescope_keybindings = false, enable_lsp_keybindings = false, + enable_preview_keybindings = false, leader = "", localleader = "" }, @@ -45,6 +53,11 @@ M.defaults = { enabled = true, setup_config = true, load_extensions = { "ui-select", "emoji" } + }, + preview = { + output_dir = vim.fn.expand("~/tmp/preview"), + temp_dir = vim.fn.expand("/tmp"), + auto_open = false } } @@ -80,6 +93,18 @@ local function validate_config(opts) end end + if opts.preview then + if opts.preview.output_dir and type(opts.preview.output_dir) ~= "string" then + return false, "preview.output_dir must be a string" + end + if opts.preview.temp_dir and type(opts.preview.temp_dir) ~= "string" then + return false, "preview.temp_dir must be a string" + end + if opts.preview.auto_open ~= nil and type(opts.preview.auto_open) ~= "boolean" then + return false, "preview.auto_open must be a boolean" + end + end + return true, nil end diff --git a/lua/iwe/health.lua b/lua/iwe/health.lua index f1a868f..8e85b3a 100644 --- a/lua/iwe/health.lua +++ b/lua/iwe/health.lua @@ -77,6 +77,7 @@ local function check_configuration() health.info(string.format('Markdown mappings enabled: %s', config.mappings.enable_markdown_mappings)) health.info(string.format('Telescope keybindings enabled: %s', config.mappings.enable_telescope_keybindings)) health.info(string.format('LSP keybindings enabled: %s', config.mappings.enable_lsp_keybindings)) + health.info(string.format('Preview keybindings enabled: %s', config.mappings.enable_preview_keybindings)) health.info(string.format('Leader key: %s', config.mappings.leader)) health.info(string.format('Local leader key: %s', config.mappings.localleader)) @@ -84,6 +85,11 @@ local function check_configuration() health.info(string.format('Telescope enabled: %s', config.telescope.enabled)) health.info(string.format('Telescope setup config: %s', config.telescope.setup_config)) health.info(string.format('Telescope extensions: %s', table.concat(config.telescope.load_extensions, ', '))) + + -- Check preview configuration + health.info(string.format('Preview output dir: %s', config.preview.output_dir)) + health.info(string.format('Preview temp dir: %s', config.preview.temp_dir)) + health.info(string.format('Preview auto open: %s', config.preview.auto_open)) end ---Check dependencies @@ -133,6 +139,80 @@ local function check_dependencies() end end +---Check preview functionality +local function check_preview() + health.start('Preview Dependencies') + + -- Check for iwe CLI + if vim.fn.executable('iwe') == 1 then + health.ok('iwe CLI found in PATH') + + local iwe_path = vim.fn.exepath('iwe') + if iwe_path and iwe_path ~= '' then + health.info(string.format('iwe CLI location: %s', iwe_path)) + end + else + health.error('iwe CLI not found in PATH', { + 'Install the IWE CLI from https://github.com/iwe-org/iwe', + 'Make sure iwe is in your PATH', + 'Preview functionality requires the iwe CLI' + }) + end + + -- Check for neato (Graphviz) + if vim.fn.executable('neato') == 1 then + health.ok('neato (Graphviz) found in PATH') + + local neato_path = vim.fn.exepath('neato') + if neato_path and neato_path ~= '' then + health.info(string.format('neato location: %s', neato_path)) + end + else + health.error('neato (Graphviz) not found in PATH', { + 'Install Graphviz package for your system', + 'macOS: brew install graphviz', + 'Ubuntu/Debian: sudo apt install graphviz', + 'Preview SVG generation requires neato' + }) + end + + -- Check preview output directory + local config = require('iwe.config').get() + local output_dir = config.preview.output_dir + + if vim.fn.isdirectory(output_dir) == 1 then + health.ok(string.format('Preview output directory exists: %s', output_dir)) + else + -- Check if parent directory is writable for creation + local parent_dir = vim.fn.fnamemodify(output_dir, ':h') + if vim.fn.isdirectory(parent_dir) == 1 and vim.fn.filewritable(parent_dir) == 2 then + health.ok(string.format('Preview output directory can be created: %s', output_dir)) + else + health.warn(string.format('Cannot create preview output directory: %s', output_dir), { + 'Check that parent directory exists and is writable', + 'Update preview.output_dir configuration if needed' + }) + end + end + + -- Test preview functionality if available + local preview = require('iwe.preview') + if preview.is_available() then + health.ok('Preview functionality available') + + local status = preview.get_status() + if status.current_file_key then + health.info(string.format('Current file key: %s', status.current_file_key)) + else + health.info('No current file key (save current buffer to enable file-specific previews)') + end + else + health.warn('Preview functionality not available', { + 'Ensure both iwe CLI and neato are installed and in PATH' + }) + end +end + ---Check current LSP status local function check_lsp_status() health.start('LSP Status') @@ -160,6 +240,7 @@ function M.check() check_project_structure() check_configuration() check_dependencies() + check_preview() check_lsp_status() end diff --git a/lua/iwe/mappings.lua b/lua/iwe/mappings.lua index eff876c..0fb4c35 100644 --- a/lua/iwe/mappings.lua +++ b/lua/iwe/mappings.lua @@ -76,11 +76,18 @@ function M.setup_plug_mappings() desc = 'Namespace symbols - roots (ga equivalent)' }) + create_plug_mapping('telescope-blockreferences', function() + require('iwe.telescope').pickers.blockreferences() + end, 'n', { + silent = true, + desc = 'LSP references - blockreferences (gr equivalent)' + }) + create_plug_mapping('telescope-backlinks', function() require('iwe.telescope').pickers.backlinks() end, 'n', { silent = true, - desc = 'LSP references - backlinks (gr equivalent)' + desc = 'LSP references - backlinks (gR equivalent)' }) create_plug_mapping('telescope-headers', function() @@ -97,6 +104,35 @@ function M.setup_plug_mappings() desc = 'Live grep search (g/ equivalent)' }) + -- Preview mappings + create_plug_mapping('preview-squash', function() + require('iwe.preview').generate_squash_preview() + end, 'n', { + silent = true, + desc = 'Generate squash markdown preview' + }) + + create_plug_mapping('preview-export', function() + require('iwe.preview').generate_export_preview() + end, 'n', { + silent = true, + desc = 'Generate export graph preview' + }) + + create_plug_mapping('preview-export-headers', function() + require('iwe.preview').generate_export_headers_preview() + end, 'n', { + silent = true, + desc = 'Generate export graph with headers preview' + }) + + create_plug_mapping('preview-export-workspace', function() + require('iwe.preview').generate_export_workspace_preview() + end, 'n', { + silent = true, + desc = 'Generate workspace graph preview' + }) + -- LSP action mappings create_plug_mapping('lsp-extract-section', function() vim.lsp.buf.code_action({apply = true, context = { only = {"refactor.extract.section"}}}) @@ -164,7 +200,8 @@ function M.setup_markdown_mappings() vim.keymap.set('n', 'gs', '(iwe-telescope-paths)', { buffer = buf }) vim.keymap.set('n', 'ga', '(iwe-telescope-roots)', { buffer = buf }) vim.keymap.set('n', 'g/', '(iwe-telescope-grep)', { buffer = buf }) - vim.keymap.set('n', 'gr', '(iwe-telescope-backlinks)', { buffer = buf }) + vim.keymap.set('n', 'gr', '(iwe-telescope-blockreferences)', { buffer = buf }) + vim.keymap.set('n', 'gR', '(iwe-telescope-backlinks)', { buffer = buf }) vim.keymap.set('n', 'go', '(iwe-telescope-headers)', { buffer = buf }) end @@ -175,6 +212,14 @@ function M.setup_markdown_mappings() vim.keymap.set('n', opts.mappings.leader .. 'h', '(iwe-lsp-rewrite-list-section)', { buffer = buf }) vim.keymap.set('n', opts.mappings.leader .. 'l', '(iwe-lsp-rewrite-section-list)', { buffer = buf }) end + + -- Preview keybindings (if enabled) + if config.get().mappings.enable_preview_keybindings then + vim.keymap.set('n', opts.mappings.leader .. 'ps', '(iwe-preview-squash)', { buffer = buf }) + vim.keymap.set('n', opts.mappings.leader .. 'pe', '(iwe-preview-export)', { buffer = buf }) + vim.keymap.set('n', opts.mappings.leader .. 'ph', '(iwe-preview-export-headers)', { buffer = buf }) + vim.keymap.set('n', opts.mappings.leader .. 'pw', '(iwe-preview-export-workspace)', { buffer = buf }) + end end, group = vim.api.nvim_create_augroup('IWE_MarkdownMappings', { clear = true }), desc = 'Setup IWE markdown mappings for markdown files' diff --git a/lua/iwe/preview.lua b/lua/iwe/preview.lua new file mode 100644 index 0000000..2fcbf76 --- /dev/null +++ b/lua/iwe/preview.lua @@ -0,0 +1,219 @@ +---@class IWE.Preview +local M = {} + +local config = require('iwe.config') + +---Get the current file key (filename without extension) +---@return string|nil +local function get_current_file_key() + local bufname = vim.api.nvim_buf_get_name(0) + if bufname == '' then + return nil + end + + local filename = vim.fn.fnamemodify(bufname, ':t:r') + if filename == '' then + return nil + end + + return filename +end + +---Check if required dependencies are available +---@return boolean success +---@return string? error_message +local function check_dependencies() + -- Check for iwe CLI + if vim.fn.executable('iwe') == 0 then + return false, "iwe CLI not found in PATH. Please install IWE CLI." + end + + -- Check for neato (Graphviz) + if vim.fn.executable('neato') == 0 then + return false, "neato not found in PATH. Please install Graphviz." + end + + return true, nil +end + +---Ensure output directory exists +---@param output_dir string +---@return boolean success +local function ensure_output_dir(output_dir) + local success = vim.fn.mkdir(output_dir, 'p') + return success == 1 or vim.fn.isdirectory(output_dir) == 1 +end + +---Execute command asynchronously and handle output +---@param cmd string[] +---@param output_file string +---@param on_success function +---@param on_error function +local function execute_async(cmd, output_file, on_success, on_error) + local success, error_msg = check_dependencies() + if not success then + on_error(error_msg) + return + end + + vim.system(cmd, { + stdout = output_file and vim.uv.fs_open(output_file, 'w', 420) or nil, + stderr = true, + }, function(result) + vim.schedule(function() + if result.code == 0 then + on_success(output_file) + else + local error_message = result.stderr and result.stderr or "Command failed with exit code " .. result.code + on_error(error_message) + end + end) + end) +end + +---Generate squashed markdown preview +---@param file_key? string Optional file key, uses current buffer if not provided +function M.generate_squash_preview(file_key) + local key = file_key or get_current_file_key() + if not key then + vim.notify("No file key available. Save the current buffer or provide a key.", vim.log.levels.ERROR) + return + end + + local preview_config = config.get().preview + local output_dir = preview_config.output_dir + + if not ensure_output_dir(output_dir) then + vim.notify("Failed to create output directory: " .. output_dir, vim.log.levels.ERROR) + return + end + + local output_file = output_dir .. "/" .. key .. "-preview.md" + local cmd = { 'iwe', 'squash', '-d', '3', '--key', key } + + execute_async(cmd, output_file, function(file) + vim.notify("Squash preview generated: " .. file) + if preview_config.auto_open then + vim.cmd('edit ' .. file) + end + end, function(error) + vim.notify("Failed to generate squash preview: " .. error, vim.log.levels.ERROR) + end) +end + +---Generate basic export DOT SVG +---@param file_key? string Optional file key, uses current buffer if not provided +function M.generate_export_preview(file_key) + local key = file_key or get_current_file_key() + if not key then + vim.notify("No file key available. Save the current buffer or provide a key.", vim.log.levels.ERROR) + return + end + + local preview_config = config.get().preview + local output_dir = preview_config.output_dir + + if not ensure_output_dir(output_dir) then + vim.notify("Failed to create output directory: " .. output_dir, vim.log.levels.ERROR) + return + end + + local output_file = output_dir .. "/" .. key .. "-graph.svg" + local cmd = { 'sh', '-c', + string.format('iwe export dot --key %s -d 2 | neato -Tsvg -o %s', + vim.fn.shellescape(key), vim.fn.shellescape(output_file)) } + + execute_async(cmd, nil, function() + vim.notify("Export preview generated: " .. output_file) + if preview_config.auto_open then + -- Try to open SVG with system default application + vim.system({ 'open', output_file }, {}, function() end) + end + end, function(error) + vim.notify("Failed to generate export preview: " .. error, vim.log.levels.ERROR) + end) +end + +---Generate export DOT SVG with headers +---@param file_key? string Optional file key, uses current buffer if not provided +function M.generate_export_headers_preview(file_key) + local key = file_key or get_current_file_key() + if not key then + vim.notify("No file key available. Save the current buffer or provide a key.", vim.log.levels.ERROR) + return + end + + local preview_config = config.get().preview + local output_dir = preview_config.output_dir + + if not ensure_output_dir(output_dir) then + vim.notify("Failed to create output directory: " .. output_dir, vim.log.levels.ERROR) + return + end + + local output_file = output_dir .. "/" .. key .. "-headers.svg" + local cmd = { 'sh', '-c', + string.format('iwe export dot --key %s -d 2 --include-headers | neato -Tsvg -o %s', + vim.fn.shellescape(key), vim.fn.shellescape(output_file)) } + + execute_async(cmd, nil, function() + vim.notify("Export headers preview generated: " .. output_file) + if preview_config.auto_open then + -- Try to open SVG with system default application + vim.system({ 'open', output_file }, {}, function() end) + end + end, function(error) + vim.notify("Failed to generate export headers preview: " .. error, vim.log.levels.ERROR) + end) +end + +---Generate full workspace export DOT SVG +function M.generate_export_workspace_preview() + local preview_config = config.get().preview + local output_dir = preview_config.output_dir + + if not ensure_output_dir(output_dir) then + vim.notify("Failed to create output directory: " .. output_dir, vim.log.levels.ERROR) + return + end + + local output_file = output_dir .. "/workspace.svg" + local cmd = { 'sh', '-c', + string.format('iwe export dot -d 1 | neato -Tsvg -o %s', + vim.fn.shellescape(output_file)) } + + execute_async(cmd, nil, function() + vim.notify("Workspace preview generated: " .. output_file) + if preview_config.auto_open then + -- Try to open SVG with system default application + vim.system({ 'open', output_file }, {}, function() end) + end + end, function(error) + vim.notify("Failed to generate workspace preview: " .. error, vim.log.levels.ERROR) + end) +end + +---Check if preview functionality is available +---@return boolean +function M.is_available() + local success, _ = check_dependencies() + return success +end + +---Get preview status information +---@return table +function M.get_status() + local status = { + iwe_available = vim.fn.executable('iwe') == 1, + neato_available = vim.fn.executable('neato') == 1, + output_dir = config.get().preview.output_dir, + current_file_key = get_current_file_key() + } + + status.output_dir_writable = ensure_output_dir(status.output_dir) + status.ready = status.iwe_available and status.neato_available and status.output_dir_writable + + return status +end + +return M \ No newline at end of file diff --git a/lua/iwe/telescope.lua b/lua/iwe/telescope.lua index ec910a2..abfbeb5 100644 --- a/lua/iwe/telescope.lua +++ b/lua/iwe/telescope.lua @@ -203,7 +203,28 @@ function pickers.roots() }) end ----LSP references - backlinks (equivalent to gr) +---LSP references - block references (equivalent to gr) +function pickers.blockreferences() + if not M.is_available() then + vim.notify("Telescope not available", vim.log.levels.ERROR) + return + end + + require('telescope.builtin').lsp_references({ + prompt_title = "IWE Block references", + layout_config = { + horizontal = { + prompt_position = "top", + preview_width = 0.7, + width = 0.9, + height = 0.9, + }, + }, + include_declaration = false, + }) +end + +---LSP references - backlinks (equivalent to gR) function pickers.backlinks() if not M.is_available() then vim.notify("Telescope not available", vim.log.levels.ERROR) @@ -220,6 +241,7 @@ function pickers.backlinks() height = 0.9, }, }, + include_declaration = true, }) end