[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nmax_line_length = 100\nend_of_line = lf\ncharset = utf-8\ninsert_final_newline = true\nindent_style = tab\nindent_size = 3\ntab_width = 3\ntrim_trailing_whitespace = true\n\n[*.{yml,yaml,scm,cff}]\nindent_style = space\nindent_size = 2\ntab_width = 2\n\n[*.py]\nindent_style = space\nindent_size = 4\ntab_width = 4\n\n[*.md]\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".emmyrc.json",
    "content": "{\n\t\"runtime\": {\n\t\t\"version\": \"LuaJIT\",\n\t\t\"requirePattern\": [\"lua/?.lua\", \"lua/?/init.lua\"]\n\t},\n\t\"workspace\": {\n\t\t\"library\": [\"$VIMRUNTIME\"]\n\t},\n\t\"$schema\": \"https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json\"\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository\n\ngithub: chrisgrieser\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ndescription: File a bug report\ntitle: \"[Bug]: \"\nlabels: [\"bug\"]\nbody:\n  - type: textarea\n    id: bug-description\n    attributes:\n      label: Bug Description\n      description: A clear and concise description of the bug.\n    validations:\n      required: true\n  - type: textarea\n    id: screenshot\n    attributes:\n      label: Relevant Screenshot\n      description: If applicable, add screenshots or a screen recording to help explain your problem.\n  - type: textarea\n    id: reproduction-steps\n    attributes:\n      label: To Reproduce\n      description: Steps to reproduce the problem\n      placeholder: |\n        For example:\n        1. Go to '...'\n        2. Click on '...'\n        3. Scroll down to '...'\n    validations:\n      required: true\n  - type: textarea\n    id: version-info\n    attributes:\n      label: neovim version\n      render: Text\n    validations:\n      required: true\n  - type: checkboxes\n    id: checklist\n    attributes:\n      label: Make sure you have done the following\n      options:\n        - label: I have updated to the latest version of the plugin.\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request\ndescription: Suggest an idea\ntitle: \"Feature Request: \"\nlabels: [\"enhancement\"]\nbody:\n  - type: checkboxes\n    id: checklist\n    attributes:\n      label: Checklist\n      options:\n        - label: \"I have read the plugin's documentation.\"\n          required: true\n        - label: The feature would be useful to more users than just me.\n          required: true\n  - type: textarea\n    id: feature-requested\n    attributes:\n      label: Feature Requested\n      description: A clear and concise description of the feature.\n    validations:\n      required: true\n  - type: textarea\n    id: screenshot\n    attributes:\n      label: Relevant Screenshot\n      description: If applicable, add screenshots or a screen recording to help explain the request.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    commit-message:\n      prefix: \"chore(dependabot): \"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Problem statement\n<!-- Briefly describe the issue this PR addresses. -->\n\n## Proposed solution\n<!-- Explain how this PR resolves the problem. -->\n\n## AI usage disclosure\n<!-- If you used AI beyond simple autocomplete, describe how.\nAI-assisted code is not discouraged if it has been properly reviewed;\nthis disclosure is for transparency. -->\n\n## Checklist\n- [ ] Variable names follow `camelCase` convention.\n- [ ] All AI-generated code has been reviewed by a human.\n- [ ] The `README.md` has been updated for any new or modified functionality\n  (the `.txt` file is auto-generated and does not need to be modified).\n"
  },
  {
    "path": ".github/workflows/nvim-type-check.yml",
    "content": "name: nvim type check\n\non:\n  push:\n    branches: [main]\n    paths: [\"**.lua\"]\n  pull_request: \n    paths: [\"**.lua\"]\n\njobs:\n  build:\n    name: nvim type check\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: stevearc/nvim-typecheck-action@v2\n"
  },
  {
    "path": ".github/workflows/panvimdoc.yml",
    "content": "name: panvimdoc\n\non:\n  push:\n    branches: [main]\n    paths:\n      - README.md\n      - .github/workflows/panvimdoc.yml\n  workflow_dispatch: {} # allows manual execution\n\npermissions:\n  contents: write\n\n#───────────────────────────────────────────────────────────────────────────────\n\njobs:\n  docs:\n    runs-on: ubuntu-latest\n    name: README.md to vimdoc\n    steps:\n      - uses: actions/checkout@v6\n      - run: git pull # fix failure when multiple commits are pushed in succession\n      - run: mkdir -p doc\n\n      - name: panvimdoc\n        uses: kdheepak/panvimdoc@main\n        with:\n          vimdoc: ${{ github.event.repository.name }}\n          version: \"Neovim\"\n          demojify: true\n          treesitter: true\n\n      - run: git pull\n      - name: push changes\n        uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          commit_message: \"chore: auto-generate vimdocs\"\n          branch: ${{ github.head_ref }}\n"
  },
  {
    "path": ".github/workflows/pr-title.yml",
    "content": "name: PR title\n\non:\n  pull_request_target:\n    types:\n      - opened\n      - edited\n      - synchronize\n      - reopened\n      - ready_for_review\n\npermissions:\n  pull-requests: read\n\njobs:\n  semantic-pull-request:\n    name: Check PR title\n    runs-on: ubuntu-latest\n    steps:\n      - uses: amannn/action-semantic-pull-request@v6\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          requireScope: false\n          subjectPattern: ^(?![A-Z]).+$ # disallow title starting with capital\n          types: | # add `improv` to the list of allowed types\n            improv\n            fix\n            feat\n            refactor\n            build\n            ci\n            style\n            test\n            chore\n            perf\n            docs\n            break\n            revert\n"
  },
  {
    "path": ".github/workflows/rumdl-lint.yml",
    "content": "name: Markdown linting via rumdl\n\non:\n  push:\n    branches: [main]\n    paths:\n      - \"**/*.md\"\n      - \".github/workflows/rumdl-lint.yml\"\n      - \".rumdl.toml\"\n  pull_request:\n    paths:\n      - \"**/*.md\"\n\njobs:\n  rumdl:\n    name: rumdl\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: rvben/rumdl@v0\n        with:\n          report-type: annotations\n"
  },
  {
    "path": ".github/workflows/stale-bot.yml",
    "content": "name: Stale bot\non:\n  schedule:\n    - cron: \"18 04 * * 3\" \n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Close stale issues\n        uses: actions/stale@v10\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n\n          # DOCS https://github.com/actions/stale#all-options\n          days-before-stale: 180\n          days-before-close: 7\n          stale-issue-label: \"Stale\"\n          stale-issue-message: |\n            This issue has been automatically marked as stale.\n            **If this issue is still affecting you, please leave any comment**, for example \"bump\", and it will be kept open.\n          close-issue-message: |\n            This issue has been closed due to inactivity, and will not be monitored.\n"
  },
  {
    "path": ".github/workflows/stylua.yml",
    "content": "name: Stylua check\n\non:\n  push:\n    branches: [main]\n    paths: [\"**.lua\"]\n  pull_request:\n    paths: [\"**.lua\"]\n\njobs:\n  stylua:\n    name: Stylua\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: JohnnyMorganz/stylua-action@v5\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          version: latest\n          args: --check .\n"
  },
  {
    "path": ".gitignore",
    "content": "# help-tags auto-generated by lazy.nvim\ndoc/tags\n"
  },
  {
    "path": ".harper-dictionary.txt",
    "content": "DAP\n"
  },
  {
    "path": ".luarc.jsonc",
    "content": "{\n\t\"runtime.version\": \"LuaJIT\",\n\n\t\"workspace.library\": [\"$VIMRUNTIME/lua\"], // nvim-lua runtime\n\n\t\"diagnostics\": {\n\t\t\"unusedLocalExclude\": [\"_*\"], // allow `_varname` for unused variables\n\t\t\"groupFileStatus\": {\n\t\t\t\"luadoc\": \"Any\", // require stricter annotations\n\t\t\t\"conventions\": \"Any\" // disallow global variables\n\t\t}\n\t},\n\n\t\"$schema\": \"https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json\"\n}\n"
  },
  {
    "path": ".rumdl.toml",
    "content": "# DOCS https://github.com/rvben/rumdl/blob/main/docs/global-settings.md\n\n[global]\nline-length = 80\ndisable = [\"blanks-around-lists\"]  # rule of proximity\n\n# ------------------------------------------------------------------------------\n\n[ul-style]\nstyle = \"dash\"  # GitHub default & quicker to type\n\n[ul-indent]\nindent = 4  # consistent with .editorconfig\n\n[line-length]\ncode-blocks = false\nreflow = true  # enable auto-formatting\n\n[blanks-around-headings]\nlines-below = 0  # rule of proximity\n\n[ol-prefix]\nstyle = \"ordered\"\n\n[no-inline-html]\nallowed-elements = [\"a\", \"img\", \"kbd\"]  # badges\n\n[emphasis-style]\nstyle = \"asterisk\"  # better than underscore, since not considered a word-char\n\n[strong-style]\nstyle = \"asterisk\"\n\n[table-format]\nenabled = true  # opt-in rule\n\n[heading-capitalization]\nenabled = true  # opt-in rule\nstyle = \"sentence_case\"\nignore-words = [\"nvim\", \"Obsidian\", \"Alfred\"]\n\n[toc-validation]\nenabled = true  # opt-in rule\n\n# ------------------------------------------------------------------------------\n\n[per-file-ignores]\n\".github/pull_request_template.md\" = [\"first-line-h1\"]\n"
  },
  {
    "path": ".stylua.toml",
    "content": "# https://github.com/JohnnyMorganz/StyLua#options\n#───────────────────────────────────────────────────────────────────────────────\nsyntax = \"LuaJIT\"  # needed to support `::labels::`\ncolumn_width = 100\nline_endings = \"Unix\"\nindent_type = \"Tabs\"\nindent_width = 3\nquote_style = \"AutoPreferDouble\"\ncall_parentheses = \"NoSingleTable\"\ncollapse_simple_statement = \"Always\"\nsort_requires.enabled = true\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Christopher Grieser\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Nvim-recorder 📹 <!-- rumdl-disable-line MD063 -->\n<a href=\"https://dotfyle.com/plugins/chrisgrieser/nvim-recorder\">\n<img alt=\"badge\" src=\"https://dotfyle.com/plugins/chrisgrieser/nvim-recorder/shield\"/></a>\n\nEnhance the usage of macros in Neovim.\n\n<!-- toc -->\n- [Features](#features)\n- [Setup](#setup)\n    - [Installation](#installation)\n    - [Configuration](#configuration)\n    - [Status line components](#status-line-components)\n- [Basic usage](#basic-usage)\n- [Advanced usage](#advanced-usage)\n    - [Performance optimizations](#performance-optimizations)\n    - [Macro breakpoints](#macro-breakpoints)\n    - [Lazy-loading the plugin](#lazy-loading-the-plugin)\n- [About the developer](#about-the-developer)\n<!-- tocstop -->\n\n## Features\n- **Simplified controls**: One key to start and stop recording, a second key for\n  playing the macro. Instead of `qa … q @a @@`, you just do `q … q Q Q`.[^1]\n- **Macro Breakpoints** for easier debugging of macros. Breakpoints can also be\n  set after the recording and are automatically ignored when triggering a macro\n  with a count.\n- **Status line components**: Particularly useful if you use `cmdheight=0` where\n  the recording status is not visible.\n- **Macro-to-Mapping**: Copy a macro, so you can save it as a mapping.\n- **Various quality-of-life features**: notifications with macro content, the\n  ability to cancel a recording, a command to edit macros,\n- **Performance Optimizations for large macros**: When the macro is triggered\n  with a high count, temporarily enable some performance improvements.\n- Uses up-to-date nvim features like `vim.notify`. This means you can get\n  confirmation notices with plugins like\n  [nvim-notify](https://github.com/rcarriga/nvim-notify).\n\n## Setup\n\n### Installation\n\n```lua\n-- lazy.nvim\n{\n\t\"chrisgrieser/nvim-recorder\",\n\tdependencies = \"rcarriga/nvim-notify\", -- optional\n\topts = {}, -- required even with default settings, since it calls `setup()`\n},\n\n-- packer\nuse {\n\t\"chrisgrieser/nvim-recorder\",\n\trequires = \"rcarriga/nvim-notify\", -- optional\n\tconfig = function() require(\"recorder\").setup() end,\n}\n```\n\nCalling `setup()` (or `lazy`'s `opts`) is **required**.\n\n### Configuration\n\n```lua\n-- default values\nrequire(\"recorder\").setup {\n\t-- Named registers where macros are saved (single lowercase letters only).\n\t-- The first register is the default register used as macro-slot after\n\t-- startup.\n\tslots = { \"a\", \"b\" },\n\n    -- specify one of options: \n    -- [static]   -> use static slots, this is default behaviour\n    -- [rotate]   -> rotates through letters specified in slots[]\n    dynamicSlots = \"static\",\n\n\tmapping = {\n\t\tstartStopRecording = \"q\",\n\t\tplayMacro = \"Q\",\n\t\tswitchSlot = \"<C-q>\",\n\t\teditMacro = \"cq\",\n\t\tdeleteAllMacros = \"dq\",\n\t\tyankMacro = \"yq\",\n\t\t-- ⚠️ this should be a string you don't use in insert mode during a macro\n\t\taddBreakPoint = \"##\",\n\t},\n\n\t-- Clears all macros-slots on startup.\n\tclear = false,\n\n\t-- Log level used for non-critical notifications; mostly relevant for nvim-notify.\n\t-- (Note that by default, nvim-notify does not show the levels `trace` & `debug`.)\n\tlogLevel = vim.log.levels.INFO, -- :help vim.log.levels\n\n\t-- If enabled, only essential notifications are sent.\n\t-- If you do not use a plugin like nvim-notify, set this to `true`\n\t-- to remove otherwise annoying messages.\n\tlessNotifications = false,\n\n\t-- Use nerdfont icons in the status bar components and keymap descriptions\n\tuseNerdfontIcons = true,\n\n\t-- Performance optimizations for macros with high count. When `playMacro` is\n\t-- triggered with a count higher than the threshold, nvim-recorder\n\t-- temporarily changes changes some settings for the duration of the macro.\n\tperformanceOpts = {\n\t\tcountThreshold = 100,\n\t\tlazyredraw = true, -- enable lazyredraw (see `:h lazyredraw`)\n\t\tnoSystemClipboard = true, -- remove `+`/`*` from clipboard option\n\t\tautocmdEventsIgnore = { -- temporarily ignore these autocmd events\n\t\t\t\"TextChangedI\",\n\t\t\t\"TextChanged\",\n\t\t\t\"InsertLeave\",\n\t\t\t\"InsertEnter\",\n\t\t\t\"InsertCharPre\",\n\t\t},\n\t},\n\n\t-- [experimental] partially share keymaps with nvim-dap.\n\t-- (See README for further explanations.)\n\tdapSharedKeymaps = false,\n}\n```\n\nIf you want to handle multiple macros or use `cmdheight=0`, it is recommended to\nalso set up the status line components:\n\n### Status line components\n\n```lua\n-- Indicates whether you are currently recording. Useful if you are using\n-- `cmdheight=0`, where recording-status is not visible.\nrequire(\"recorder\").recordingStatus()\n\n-- Displays non-empty macro-slots (registers) and indicates the selected ones.\n-- Only displayed when *not* recording. Slots with breakpoints get an extra `#`.\nrequire(\"recorder\").displaySlots()\n```\n\n> [!TIP]\n> Use with the config `clear = true` to see recordings you made this session.\n\nExample for adding the status line components to [lualine](https://github.com/nvim-lualine/lualine.nvim):\n\n```lua\nlualine_y = {\n\t{ require(\"recorder\").displaySlots },\n},\nlualine_z = {\n\t{ require(\"recorder\").recordingStatus },\n},\n```\n\n> [!TIP]\n> Put the components in different status line segments, so they have\n> a different color, making the recording status more distinguishable\n> from saved recordings\n\n## Basic usage\n- `startStopRecording`: Starts recording to the current macro slot (so you do\n  not need to specify a register). Press again to end the recording.\n- `playMacro`: Plays the macro in the current slot (without the need to specify\n  a register).\n- `switchSlot`: Cycles through the registers you specified in the configuration.\n  Also show a notification with the slot and its content. (The currently\n  selected slot can be seen in the [status line\n  component](#status-line-components).)\n- `editMacro`: Edit the macro recorded in the active slot. (Be aware that these\n  are the keystrokes in \"encoded\" form.)\n- `yankMacro`: Copies the current macro in decoded form that can be used to\n  create a mapping from it. Breakpoints are removed from the copied macro.\n- `deleteAllMacros`: Copies the current macro in decoded form that can be used\n  to\n\n> [!TIP]\n> For recursive macros (playing a macro inside a macro), you can still use\n> the default command `@a`.\n\n## Advanced usage\n\n### Performance optimizations\nRunning macros with a high count can be demanding on the system and result in\nlags. For this reason, `nvim-recorder` provides some performance optimizations\nthat are temporarily enabled when a macro with a high count is run.\n\nNote that these optimizations do have some potential drawbacks.\n- [`lazyredraw`](https://neovim.io/doc/user/options.html#'lazyredraw') disables\n  redrawing of the screen, which makes it harder to notice edge cases not\n  considered in the macro. It may also appear as if the screen is frozen for a\n  while.\n- Disabling the system clipboard is mostly safe, if you do not intend to copy\n  content to it with the macro.\n- Ignoring auto-commands is not recommended, when you rely on certain plugin\n  functionality during the macro, since it can potentially disrupt those\n  plugins' effect.\n\n### Macro breakpoints\n`nvim-recorder` allows you to set breakpoints in your macros which can be\nhelpful for debugging macros. Breakpoints are automatically ignored when you\ntrigger the macro with a count.\n\n**Setting Breakpoints** <!-- rumdl-disable-line MD036 -->\n- *During a recording:* press the `addBreakPoint` key (default: `##`) in normal\n   mode\n- *After a recording:* use `editMacro` and add or remove the `##` manually.\n\n**Playing Macros with Breakpoints** <!-- rumdl-disable-line MD036 -->\n- Using the `playMacro` key, the macro automatically stops at the next\n  breakpoint. The next time you press `playMacro`, the next segment of the macro\n  is played.\n- Starting a new recording, editing a macro, yanking a macro, or switching macro\n  slot all reset the sequence, meaning that `playMacro` starts from the\n  beginning again.\n\n> [!TIP]\n> You can do other things in between playing segments of the macro, like\n> moving a few characters to the left or right. That way you can also use\n> breakpoints to manually correct irregularities.\n\n**Ignoring Breakpoints** <!-- rumdl-disable-line MD036 -->\nWhen you play the macro with a *count* (for example `50Q`), breakpoints are\nautomatically ignored.\n\n> [!TIP]\n> Add a count of 1 (`1Q`) to play a macro once and still ignore breakpoints.\n\n**Shared Keybindings with `nvim-dap`**  \nIf you are using [nvim-dap](https://github.com/mfussenegger/nvim-dap), you can\nuse `dapSharedKeymaps = true` to set up the following shared keybindings:\n1. `addBreakPoint` maps to `dap.toggle_breakpoint()` outside\na recording. During a recording, it adds a macro breakpoint instead.\n2. `playMacro` maps to `dap.continue()` if there is at least one\nDAP-breakpoint. If there is no DAP-breakpoint, plays the current\nmacro-slot instead.\n\nNote that this feature is experimental, since the [respective API from nvim-dap\nis non-public and can be changed without deprecation\nnotice](https://github.com/mfussenegger/nvim-dap/discussions/810#discussioncomment-4623606).\n\n### Lazy-loading the plugin\n`nvim-recorder` is best lazy-loaded on the mappings for `startStopRecording` and\n`playMacro`. However, adding the status line components to `lualine` will cause\nthe plugin to load before you start or play a recording.\n\nTo avoid this, the status line components need to be loaded only in the plugin's\n`config`. The drawback of this method is that no component is shown when until\nyou start or play a recording (which you can completely disregard when you set\n`clear = true`, though).\n\nNonetheless, the plugin is pretty lightweight (~400 lines of code), so not\nlazy-loading it should not have a big impact.\n\n```lua\n-- minimal config for lazy-loading with lazy.nvim\n{\n\t\"chrisgrieser/nvim-recorder\",\n\tdependencies = \"rcarriga/nvim-notify\",\n\tkeys = {\n\t\t-- these must match the keys in the mapping config below\n\t\t{ \"q\", desc = \" Start Recording\" },\n\t\t{ \"Q\", desc = \" Play Recording\" },\n\t},\n\tconfig = function()\n\t\trequire(\"recorder\").setup({\n\t\t\tmapping = {\n\t\t\t\tstartStopRecording = \"q\",\n\t\t\t\tplayMacro = \"Q\",\n\t\t\t},\n\t\t})\n\n\t\t\tlocal lualineZ = require(\"lualine\").get_config().sections.lualine_z or {}\n\t\t\tlocal lualineY = require(\"lualine\").get_config().sections.lualine_y or {}\n\t\t\ttable.insert(lualineZ, { require(\"recorder\").recordingStatus })\n\t\t\ttable.insert(lualineY, { require(\"recorder\").displaySlots })\n\n\t\t\trequire(\"lualine\").setup {\n\t\t\t\ttabline = {\n\t\t\t\t\tlualine_y = lualineY,\n\t\t\t\t\tlualine_z = lualineZ,\n\t\t\t\t},\n\t\t\t}\n\tend,\n},\n```\n\n## About the developer\nIn my day job, I am a sociologist studying the social mechanisms underlying the\ndigital economy. For my PhD project, I investigate the governance of the app\neconomy and how software ecosystems manage the tension between innovation and\ncompatibility. If you are interested in this subject, feel free to get in touch.\n\n- [Website](https://chris-grieser.de/)\n- [Mastodon](https://pkm.social/@pseudometa)\n- [ResearchGate](https://www.researchgate.net/profile/Christopher-Grieser)\n- [LinkedIn](https://www.linkedin.com/in/christopher-grieser-ba693b17a/)\n\nIf you find this project helpful, you can support me via [🩷 GitHub\nSponsors](https://github.com/sponsors/chrisgrieser?frequency=one-time).\n\n[^1]: As opposed to vim, Neovim already allows you to use `Q` to [play the last\n    recorded macro](https://neovim.io/doc/user/repeat.html#Q). Considering this,\n    the simplified controls really only save you one keystroke for one-off\n    macros. However, as opposed to Neovim's built-in controls, you can still\n    keep using `Q` for playing the not-most-recently recorded macro.\n"
  },
  {
    "path": "doc/nvim-recorder.txt",
    "content": "*nvim-recorder.txt*          For Neovim          Last change: 2026 February 06\n\n==============================================================================\nTable of Contents                            *nvim-recorder-table-of-contents*\n\n1. Nvim-recorder                                |nvim-recorder-nvim-recorder-|\n  - Features                           |nvim-recorder-nvim-recorder--features|\n  - Setup                                 |nvim-recorder-nvim-recorder--setup|\n  - Basic usage                     |nvim-recorder-nvim-recorder--basic-usage|\n  - Advanced usage               |nvim-recorder-nvim-recorder--advanced-usage|\n  - About the developer     |nvim-recorder-nvim-recorder--about-the-developer|\n\n==============================================================================\n1. Nvim-recorder                                *nvim-recorder-nvim-recorder-*\n\n\n\nEnhance the usage of macros in Neovim.\n\n- |nvim-recorder-features|\n- |nvim-recorder-setup|\n    - |nvim-recorder-installation|\n    - |nvim-recorder-configuration|\n    - |nvim-recorder-status-line-components|\n- |nvim-recorder-basic-usage|\n- |nvim-recorder-advanced-usage|\n    - |nvim-recorder-performance-optimizations|\n    - |nvim-recorder-macro-breakpoints|\n    - |nvim-recorder-lazy-loading-the-plugin|\n- |nvim-recorder-about-the-developer|\n\n\nFEATURES                               *nvim-recorder-nvim-recorder--features*\n\n- **Simplified controls**: One key to start and stop recording, a second key for\n    playing the macro. Instead of `qa … q @a @@`, you just do `q … q Q Q`.\n- **Macro Breakpoints** for easier debugging of macros. Breakpoints can also be\n    set after the recording and are automatically ignored when triggering a macro\n    with a count.\n- **Status line components**: Particularly useful if you use `cmdheight=0` where\n    the recording status is not visible.\n- **Macro-to-Mapping**: Copy a macro, so you can save it as a mapping.\n- **Various quality-of-life features**: notifications with macro content, the\n    ability to cancel a recording, a command to edit macros,\n- **Performance Optimizations for large macros**: When the macro is triggered\n    with a high count, temporarily enable some performance improvements.\n- Uses up-to-date nvim features like `vim.notify`. This means you can get\n    confirmation notices with plugins like\n    nvim-notify <https://github.com/rcarriga/nvim-notify>.\n\n\nSETUP                                     *nvim-recorder-nvim-recorder--setup*\n\n\nINSTALLATION ~\n\n>lua\n    -- lazy.nvim\n    {\n        \"chrisgrieser/nvim-recorder\",\n        dependencies = \"rcarriga/nvim-notify\", -- optional\n        opts = {}, -- required even with default settings, since it calls `setup()`\n    },\n    \n    -- packer\n    use {\n        \"chrisgrieser/nvim-recorder\",\n        requires = \"rcarriga/nvim-notify\", -- optional\n        config = function() require(\"recorder\").setup() end,\n    }\n<\n\nCalling `setup()` (or `lazy`’s `opts`) is **required**.\n\n\nCONFIGURATION ~\n\n>lua\n    -- default values\n    require(\"recorder\").setup {\n        -- Named registers where macros are saved (single lowercase letters only).\n        -- The first register is the default register used as macro-slot after\n        -- startup.\n        slots = { \"a\", \"b\" },\n    \n        -- specify one of options: \n        -- [static]   -> use static slots, this is default behaviour\n        -- [rotate]   -> rotates through letters specified in slots[]\n        dynamicSlots = \"static\",\n    \n        mapping = {\n            startStopRecording = \"q\",\n            playMacro = \"Q\",\n            switchSlot = \"<C-q>\",\n            editMacro = \"cq\",\n            deleteAllMacros = \"dq\",\n            yankMacro = \"yq\",\n            -- ⚠️ this should be a string you don't use in insert mode during a macro\n            addBreakPoint = \"##\",\n        },\n    \n        -- Clears all macros-slots on startup.\n        clear = false,\n    \n        -- Log level used for non-critical notifications; mostly relevant for nvim-notify.\n        -- (Note that by default, nvim-notify does not show the levels `trace` & `debug`.)\n        logLevel = vim.log.levels.INFO, -- :help vim.log.levels\n    \n        -- If enabled, only essential notifications are sent.\n        -- If you do not use a plugin like nvim-notify, set this to `true`\n        -- to remove otherwise annoying messages.\n        lessNotifications = false,\n    \n        -- Use nerdfont icons in the status bar components and keymap descriptions\n        useNerdfontIcons = true,\n    \n        -- Performance optimizations for macros with high count. When `playMacro` is\n        -- triggered with a count higher than the threshold, nvim-recorder\n        -- temporarily changes changes some settings for the duration of the macro.\n        performanceOpts = {\n            countThreshold = 100,\n            lazyredraw = true, -- enable lazyredraw (see `:h lazyredraw`)\n            noSystemClipboard = true, -- remove `+`/`*` from clipboard option\n            autocmdEventsIgnore = { -- temporarily ignore these autocmd events\n                \"TextChangedI\",\n                \"TextChanged\",\n                \"InsertLeave\",\n                \"InsertEnter\",\n                \"InsertCharPre\",\n            },\n        },\n    \n        -- [experimental] partially share keymaps with nvim-dap.\n        -- (See README for further explanations.)\n        dapSharedKeymaps = false,\n    }\n<\n\nIf you want to handle multiple macros or use `cmdheight=0`, it is recommended\nto also set up the status line components:\n\n\nSTATUS LINE COMPONENTS ~\n\n>lua\n    -- Indicates whether you are currently recording. Useful if you are using\n    -- `cmdheight=0`, where recording-status is not visible.\n    require(\"recorder\").recordingStatus()\n    \n    -- Displays non-empty macro-slots (registers) and indicates the selected ones.\n    -- Only displayed when *not* recording. Slots with breakpoints get an extra `#`.\n    require(\"recorder\").displaySlots()\n<\n\n\n  [!TIP] Use with the config `clear = true` to see recordings you made this\n  session.\nExample for adding the status line components to lualine\n<https://github.com/nvim-lualine/lualine.nvim>:\n\n>lua\n    lualine_y = {\n        { require(\"recorder\").displaySlots },\n    },\n    lualine_z = {\n        { require(\"recorder\").recordingStatus },\n    },\n<\n\n\n  [!TIP] Put the components in different status line segments, so they have a\n  different color, making the recording status more distinguishable from saved\n  recordings\n\nBASIC USAGE                         *nvim-recorder-nvim-recorder--basic-usage*\n\n- `startStopRecording`: Starts recording to the current macro slot (so you do\n    not need to specify a register). Press again to end the recording.\n- `playMacro`: Plays the macro in the current slot (without the need to specify\n    a register).\n- `switchSlot`: Cycles through the registers you specified in the configuration.\n    Also show a notification with the slot and its content. (The currently\n    selected slot can be seen in the |nvim-recorder-status-line-component|.)\n- `editMacro`: Edit the macro recorded in the active slot. (Be aware that these\n    are the keystrokes in \"encoded\" form.)\n- `yankMacro`: Copies the current macro in decoded form that can be used to\n    create a mapping from it. Breakpoints are removed from the copied macro.\n- `deleteAllMacros`: Copies the current macro in decoded form that can be used\n    to\n\n\n  [!TIP] For recursive macros (playing a macro inside a macro), you can still use\n  the default command `@a`.\n\nADVANCED USAGE                   *nvim-recorder-nvim-recorder--advanced-usage*\n\n\nPERFORMANCE OPTIMIZATIONS ~\n\nRunning macros with a high count can be demanding on the system and result in\nlags. For this reason, `nvim-recorder` provides some performance optimizations\nthat are temporarily enabled when a macro with a high count is run.\n\nNote that these optimizations do have some potential drawbacks. -\n|`lazyredraw`| disables redrawing of the screen, which makes it harder to\nnotice edge cases not considered in the macro. It may also appear as if the\nscreen is frozen for a while. - Disabling the system clipboard is mostly safe,\nif you do not intend to copy content to it with the macro. - Ignoring\nauto-commands is not recommended, when you rely on certain plugin functionality\nduring the macro, since it can potentially disrupt those plugins’ effect.\n\n\nMACRO BREAKPOINTS ~\n\n`nvim-recorder` allows you to set breakpoints in your macros which can be\nhelpful for debugging macros. Breakpoints are automatically ignored when you\ntrigger the macro with a count.\n\n**Setting Breakpoints** - _During a recording:_ press the `addBreakPoint` key\n(default: `##`) in normal mode - _After a recording:_ use `editMacro` and add\nor remove the `##` manually.\n\n**Playing Macros with Breakpoints** - Using the `playMacro` key, the macro\nautomatically stops at the next breakpoint. The next time you press\n`playMacro`, the next segment of the macro is played. - Starting a new\nrecording, editing a macro, yanking a macro, or switching macro slot all reset\nthe sequence, meaning that `playMacro` starts from the beginning again.\n\n\n  [!TIP] You can do other things in between playing segments of the macro, like\n  moving a few characters to the left or right. That way you can also use\n  breakpoints to manually correct irregularities.\n**Ignoring Breakpoints** When you play the macro with a _count_ (for example\n`50Q`), breakpoints are automatically ignored.\n\n\n  [!TIP] Add a count of 1 (`1Q`) to play a macro once and still ignore\n  breakpoints.\n**Shared Keybindings with nvim-dap** If you are using nvim-dap\n<https://github.com/mfussenegger/nvim-dap>, you can use `dapSharedKeymaps =\ntrue` to set up the following shared keybindings: 1. `addBreakPoint` maps to\n`dap.toggle_breakpoint()` outside a recording. During a recording, it adds a\nmacro breakpoint instead. 2. `playMacro` maps to `dap.continue()` if there is\nat least one DAP-breakpoint. If there is no DAP-breakpoint, plays the current\nmacro-slot instead.\n\nNote that this feature is experimental, since the respective API from nvim-dap\nis non-public and can be changed without deprecation notice\n<https://github.com/mfussenegger/nvim-dap/discussions/810#discussioncomment-4623606>.\n\n\nLAZY-LOADING THE PLUGIN ~\n\n`nvim-recorder` is best lazy-loaded on the mappings for `startStopRecording`\nand `playMacro`. However, adding the status line components to `lualine` will\ncause the plugin to load before you start or play a recording.\n\nTo avoid this, the status line components need to be loaded only in the\nplugin’s `config`. The drawback of this method is that no component is shown\nwhen until you start or play a recording (which you can completely disregard\nwhen you set `clear = true`, though).\n\nNonetheless, the plugin is pretty lightweight (~400 lines of code), so not\nlazy-loading it should not have a big impact.\n\n>lua\n    -- minimal config for lazy-loading with lazy.nvim\n    {\n        \"chrisgrieser/nvim-recorder\",\n        dependencies = \"rcarriga/nvim-notify\",\n        keys = {\n            -- these must match the keys in the mapping config below\n            { \"q\", desc = \" Start Recording\" },\n            { \"Q\", desc = \" Play Recording\" },\n        },\n        config = function()\n            require(\"recorder\").setup({\n                mapping = {\n                    startStopRecording = \"q\",\n                    playMacro = \"Q\",\n                },\n            })\n    \n                local lualineZ = require(\"lualine\").get_config().sections.lualine_z or {}\n                local lualineY = require(\"lualine\").get_config().sections.lualine_y or {}\n                table.insert(lualineZ, { require(\"recorder\").recordingStatus })\n                table.insert(lualineY, { require(\"recorder\").displaySlots })\n    \n                require(\"lualine\").setup {\n                    tabline = {\n                        lualine_y = lualineY,\n                        lualine_z = lualineZ,\n                    },\n                }\n        end,\n    },\n<\n\n\nABOUT THE DEVELOPER         *nvim-recorder-nvim-recorder--about-the-developer*\n\nIn my day job, I am a sociologist studying the social mechanisms underlying the\ndigital economy. For my PhD project, I investigate the governance of the app\neconomy and how software ecosystems manage the tension between innovation and\ncompatibility. If you are interested in this subject, feel free to get in\ntouch.\n\n- Website <https://chris-grieser.de/>\n- Mastodon <https://pkm.social/@pseudometa>\n- ResearchGate <https://www.researchgate.net/profile/Christopher-Grieser>\n- LinkedIn <https://www.linkedin.com/in/christopher-grieser-ba693b17a/>\n\nIf you find this project helpful, you can support me via GitHub Sponsors\n<https://github.com/sponsors/chrisgrieser?frequency=one-time>.\n\nGenerated by panvimdoc <https://github.com/kdheepak/panvimdoc>\n\nvim:tw=78:ts=8:noet:ft=help:norl:\n"
  },
  {
    "path": "lua/recorder.lua",
    "content": "local M = {}\n\nlocal fn = vim.fn\nlocal v = vim.v\nlocal opt = vim.opt\nlocal keymap = vim.keymap.set\n\n-- internal vars\nlocal config, macroRegs, slotIndex, defaultLogLevel, breakCounter, firstRun\n\n-- Use this function to normalize keycodes (which can have multiple\n-- representations, e.g. <C-f> or <C-F>).\n---@param mapping string\nlocal normalizeKeycodes = function(mapping)\n\treturn fn.keytrans(vim.api.nvim_replace_termcodes(mapping, true, true, true))\nend\n\nlocal getMacro = function(reg)\n\t-- Some keys (e.g. <C-F>) have different representations when they are recorded\n\t-- versus when they are a result of vim.api.nvim_replace_termcodes (for example).\n\t-- This ensures that whenever we are manually doing something with register contents,\n\t-- they are always consistent.\n\treturn vim.api.nvim_replace_termcodes(fn.keytrans(vim.fn.getreg(reg)), true, true, true)\nend\nlocal setMacro = function(reg, recording) vim.fn.setreg(reg, recording, \"c\") end\n\n-- vars which can be set by the user\nlocal toggleKey, breakPointKey, dapSharedKeymaps, lessNotifications, useNerdfontIcons\nlocal perf = {}\n\n--------------------------------------------------------------------------------\n\n---@param msg string\n---@param level? 0|1|2|3|4 vim.log.levels\n---@param importance \"essential\"|\"nonessential\"\n---@param extraOpts? table\nlocal function notify(msg, importance, level, extraOpts)\n\tif importance == \"nonessential\" and lessNotifications then return end\n\tif not level then level = defaultLogLevel end\n\tlocal opts = vim.tbl_deep_extend(\"force\", { title = \"nvim-recorder\" }, extraOpts or {})\n\tvim.notify(msg, level, opts)\nend\n\n---@return boolean\nlocal function isRecording() return fn.reg_recording() ~= \"\" end\n\n---@return boolean\nlocal function isPlaying() return fn.reg_executing() ~= \"\" end\n\n---runs `:normal` natively with bang\n---@param cmdStr string\nlocal function normal(cmdStr) vim.cmd.normal { cmdStr, bang = true } end\n\n--------------------------------------------------------------------------------\n-- COMMANDS\n\n-- start/stop recording macro into the current slot\nlocal function toggleRecording()\n\tif config.dynamicSlots == \"rotate\" and not firstRun and not isRecording() then\n\t\tslotIndex = slotIndex + 1\n\t\tif slotIndex > #macroRegs then slotIndex = 1 end\n\tend\n\tif firstRun then firstRun = false end\n\tlocal reg = macroRegs[slotIndex]\n\n\t-- start recording\n\tif not isRecording() then\n\t\tbreakCounter = 0 -- reset break points\n\t\tnormal(\"q\" .. reg)\n\t\tnotify(\"Recording to [\" .. reg .. \"]…\", \"essential\")\n\t\treturn\n\tend\n\n\t-- stop recording\n\tlocal prevRec = getMacro(macroRegs[slotIndex])\n\tnormal(\"q\")\n\n\t-- NOTE the macro key records itself, so it has to be removed from the\n\t-- register. As this function has to know the variable length of the\n\t-- LHS key that triggered it, it has to be passed in via .setup()-function\n\tlocal decodedToggleKey = vim.api.nvim_replace_termcodes(toggleKey, true, true, true)\n\tlocal recording = getMacro(reg):sub(1, -1 * (#decodedToggleKey + 1))\n\tsetMacro(reg, recording)\n\n\tlocal justRecorded = fn.keytrans(getMacro(reg))\n\tif justRecorded == \"\" then\n\t\tif config.dynamicSlots == \"rotate\" then slotIndex = slotIndex - 1 end\n\t\tsetMacro(reg, prevRec)\n\t\tnotify(\"Recording aborted.\\n(Previous recording is kept.)\", \"essential\")\n\telseif not lessNotifications then\n\t\tnotify(\"Recorded [\" .. reg .. \"]:\\n\" .. justRecorded, \"nonessential\")\n\tend\nend\n\n---play the macro recorded in current slot\nlocal function playRecording()\n\tlocal reg = macroRegs[slotIndex]\n\tlocal macro = getMacro(reg)\n\n\t-- Guard Clause 1: Toggle Breakpoint instead of Macro\n\t-- WARN undocumented and prone to change https://github.com/mfussenegger/nvim-dap/discussions/810#discussioncomment-4623606\n\tif dapSharedKeymaps then\n\t\t-- nested to avoid requiring `dap` for lazyloading\n\t\tlocal dapBreakpointsExist = next(require(\"dap.breakpoints\").get()) ~= nil\n\t\tif dapBreakpointsExist then\n\t\t\trequire(\"dap\").continue()\n\t\t\treturn\n\t\tend\n\tend\n\n\t-- Guard Clause 2: Recursively play macro\n\tif isRecording() then\n\t\t-- stylua: ignore\n\t\tnotify(\n\t\t\t\"Playing the macro while it is recording would cause recursion problems. Aborting.\\n\" ..\n\t\t\t\"(You can still use recursive macros by using `@\" .. reg .. \"`)\",\n\t\t\t\"essential\",\n\t\t\tvim.log.levels.ERROR\n\t\t)\n\t\tnormal(\"q\") -- end recording\n\t\tsetMacro(reg, \"\") -- empties macro since the recursion has been recorded there\n\t\treturn\n\tend\n\n\t-- Guard Clause 3: Slot is empty\n\tif macro == \"\" then\n\t\tnotify(\"Macro Slot [\" .. reg .. \"] is empty.\", \"essential\", vim.log.levels.WARN)\n\t\treturn\n\tend\n\n\t-- EXECUTE MACRO\n\tlocal countGiven = v.count ~= 0\n\tlocal hasBreakPoints = fn.keytrans(macro):find(vim.pesc(breakPointKey))\n\tlocal usePerfOptimizations = v.count1 >= perf.countThreshold\n\n\t-- macro (w/ breakpoints)\n\tif hasBreakPoints and not countGiven then\n\t\tbreakCounter = breakCounter + 1\n\n\t\tlocal macroParts = {}\n\t\tfor _, macroPart in ipairs(vim.split(fn.keytrans(macro), vim.pesc(breakPointKey), {})) do\n\t\t\ttable.insert(macroParts, vim.api.nvim_replace_termcodes(macroPart, true, true, true))\n\t\tend\n\n\t\tlocal partialMacro = macroParts[breakCounter]\n\n\t\tsetMacro(reg, partialMacro)\n\t\tnormal(\"@\" .. reg)\n\t\tsetMacro(reg, macro) -- restore original macro for all other purposes like prewing slots\n\n\t\tif breakCounter ~= #macroParts then\n\t\t\tnotify(\"Reached Breakpoint #\" .. tostring(breakCounter), \"essential\")\n\t\telse\n\t\t\tnotify(\"Reached end of macro\", \"essential\")\n\t\t\tbreakCounter = 0\n\t\tend\n\n\t-- macro (w/ perf optimizations)\n\telseif usePerfOptimizations then\n\t\t-- message to avoid confusion by the user due to performance optimizations\n\t\tlocal msg = \"Running macro with performance optimizations…\"\n\t\tif perf.lazyredraw then\n\t\t\tmsg = msg\n\t\t\t\t.. \"\\nnvim might appear to freeze due to lazy redrawing. \\nThis is to be expected and not a bug.\"\n\t\tend\n\t\tnotify(msg, \"nonessential\", nil, { animate = false }) -- no animation as macro will be blocking\n\n\t\tlocal original = {}\n\t\tif perf.lazyredraw then\n\t\t\toriginal.lazyredraw = opt.lazyredraw:get() ---@diagnostic disable-line: undefined-field\n\t\t\topt.lazyredraw = true\n\t\tend\n\t\tif perf.noSystemclipboard then\n\t\t\toriginal.clipboard = opt.clipboard:get() ---@diagnostic disable-line: undefined-field\n\t\t\topt.clipboard = \"\"\n\t\tend\n\t\toriginal.eventignore = opt.eventignore:get()\n\t\topt.eventignore = perf.autocmdEventsIgnore\n\n\t\t-- if notification is shown, defer to ensure it is displayed\n\t\tlocal count = v.count1 -- counts needs to be saved due to scoping by defer_fn\n\t\tvim.defer_fn(function()\n\t\t\tnormal(count .. \"@\" .. reg)\n\n\t\t\tif perf.lazyredraw then vim.opt.lazyredraw = original.lazyredraw end\n\t\t\tif perf.noSystemclipboard then opt.clipboard = original.clipboard end\n\t\t\topt.eventignore = original.eventignore\n\t\tend, 500)\n\n\t-- macro (regular)\n\telse\n\t\tnormal(v.count1 .. \"@\" .. reg)\n\tend\nend\n\n---changes the active slot\nlocal function switchMacroSlot()\n\tslotIndex = slotIndex + 1\n\tbreakCounter = 0 -- reset breakpoint counter\n\n\tif slotIndex > #macroRegs then slotIndex = 1 end\n\tlocal reg = macroRegs[slotIndex]\n\tlocal currentMacro = fn.keytrans(getMacro(reg))\n\tlocal msg = \" Now using macro slot [\" .. reg .. \"]\"\n\tif currentMacro ~= \"\" then\n\t\tmsg = msg .. \".\\n\" .. currentMacro\n\telse\n\t\tmsg = msg .. \"\\n(empty)\"\n\tend\n\tnotify(msg, \"nonessential\")\nend\n\n---edit the current slot\nlocal function editMacro()\n\tbreakCounter = 0 -- reset breakpoint counter\n\tlocal reg = macroRegs[slotIndex]\n\tlocal macroContent = fn.keytrans(getMacro(reg))\n\tlocal inputConfig = {\n\t\tprompt = \"Edit Macro [\" .. reg .. \"]:\",\n\t\tdefault = macroContent,\n\t}\n\tvim.ui.input(inputConfig, function(editedMacro)\n\t\tif not editedMacro then return end -- cancellation\n\t\tsetMacro(reg, vim.api.nvim_replace_termcodes(editedMacro, true, true, true))\n\t\tnotify(\"Edited Macro [\" .. reg .. \"]:\\n\" .. editedMacro, \"nonessential\")\n\tend)\nend\n\n---@param mode? \"silent\"\nlocal function deleteAllMacros(mode)\n\tbreakCounter = 0 -- reset breakpoint counter\n\tfor _, reg in pairs(macroRegs) do\n\t\tsetMacro(reg, \"\")\n\tend\n\tif mode ~= \"silent\" then notify(\"All macros deleted.\", \"nonessential\") end\nend\n\nlocal function yankMacro()\n\tbreakCounter = 0\n\tlocal reg = macroRegs[slotIndex]\n\tlocal macroContent = fn.keytrans(getMacro(reg))\n\tif macroContent == \"\" then\n\t\tnotify(\n\t\t\t\"Nothing to copy, macro slot [\" .. reg .. \"] is still empty.\",\n\t\t\t\"essential\",\n\t\t\tvim.log.levels.WARN\n\t\t)\n\t\treturn\n\tend\n\t-- remove breakpoints when yanking the macro\n\tmacroContent = macroContent:gsub(vim.pesc(breakPointKey), \"\")\n\n\tlocal clipboardOpt = opt.clipboard:get() ---@diagnostic disable-line: undefined-field\n\tlocal useSystemClipb = #clipboardOpt > 0 and clipboardOpt[1]:find(\"unnamed\")\n\tlocal copyToReg = useSystemClipb and \"+\" or '\"'\n\n\tfn.setreg(copyToReg, macroContent)\n\tnotify(\"Copied Macro [\" .. reg .. \"]:\\n\" .. macroContent, \"nonessential\")\nend\n\nlocal function addBreakPoint()\n\tif isRecording() then\n\t\t-- INFO nothing happens, but the key is still recorded in the macro\n\t\tnotify(\"Macro breakpoint added.\", \"essential\")\n\telseif not isPlaying() and not dapSharedKeymaps then\n\t\tnotify(\"Cannot insert breakpoint outside of a recording.\", \"essential\", vim.log.levels.WARN)\n\telseif not isPlaying() and dapSharedKeymaps then\n\t\t-- only test for dap here to not interfere with user lazyloading\n\t\tif require(\"dap\") then require(\"dap\").toggle_breakpoint() end\n\tend\nend\n\n--------------------------------------------------------------------------------\n-- CONFIG\n\n---@class configObj\n---@field slots string[] named register slots\n---@field dynamicSlots string 2 states we could choose from:\n---static   -> use static slots\n---rotate   -> through letters specified in slots[] if end is encountered it goes(overwrite) from start\n---@field clear boolean whether to clear slots/registers on setup\n---@field timeout number Default timeout for notification\n---@field mapping maps individual mappings\n---@field logLevel integer log level (vim.log.levels)\n---@field lessNotifications boolean plugin is less verbose, shows only essential or critical notifications\n---@field performanceOpts perfOpts various performance options\n---@field dapSharedKeymaps boolean (experimental) partially share keymaps with dap\n---@field useNerdfontIcons boolean currently only relevant for status bar components\n\n---@class perfOpts\n---@field countThreshold number if count used is higher than threshold, the following performance optimizations are applied\n---@field lazyredraw boolean :h lazyredraw\n---@field noSystemClipboard boolean no `*` or `+` in clipboard https://vi.stackexchange.com/a/31888\n---@field autocmdEventsIgnore string[] list of autocmd events to ignore\n\n---@class maps\n---@field startStopRecording string\n---@field playMacro string\n---@field editMacro string\n---@field yankMacro string\n---@field deleteAllMacros string\n---@field switchSlot string\n---@field addBreakPoint string\n\n---@param userConfig configObj\nfunction M.setup(userConfig)\n\t-- initialize values on plugin load\n\tslotIndex = 1\n\tbreakCounter = 0\n\tfirstRun = true\n\n\tlocal defaultConfig = {\n\t\tslots = { \"a\", \"b\" },\n\t\tdynamicSlots = \"static\",\n\t\tmapping = {\n\t\t\tstartStopRecording = \"q\",\n\t\t\tplayMacro = \"Q\",\n\t\t\tswitchSlot = \"<C-q>\",\n\t\t\teditMacro = \"cq\",\n\t\t\tdeleteAllMacros = \"dq\",\n\t\t\tyankMacro = \"yq\",\n\t\t\taddBreakPoint = \"##\",\n\t\t},\n\t\tdapSharedKeymaps = false,\n\t\tclear = false,\n\t\tlogLevel = vim.log.levels.INFO,\n\t\tlessNotifications = false,\n\t\tuseNerdfontIcons = true,\n\t\tperformanceOpts = {\n\t\t\tcountThreshold = 100,\n\t\t\tlazyredraw = true,\n\t\t\tnoSystemClipboard = true,\n\t\t\t-- stylua: ignore\n\t\t\tautocmdEventsIgnore = { \"TextChangedI\", \"TextChanged\", \"InsertLeave\", \"InsertEnter\", \"InsertCharPre\" },\n\t\t},\n\t}\n\tconfig = vim.tbl_deep_extend(\"keep\", userConfig, defaultConfig)\n\n\t-- settings to be used globally\n\tperf = config.performanceOpts\n\tuseNerdfontIcons = config.useNerdfontIcons\n\tlessNotifications = config.lessNotifications\n\tdefaultLogLevel = config.logLevel\n\n\t-- validate macro slots\n\tmacroRegs = config.slots\n\tfor _, reg in pairs(macroRegs) do\n\t\tif not (reg:find(\"^%l$\")) then\n\t\t\tnotify(\n\t\t\t\t('\"%s\" is an invalid slot. Choose only named registers (a-z).'):format(reg),\n\t\t\t\t\"essential\",\n\t\t\t\tvim.log.levels.ERROR\n\t\t\t)\n\t\t\treturn\n\t\tend\n\tend\n\n\t-- clear macro slots\n\tif config.clear then deleteAllMacros(\"silent\") end\n\n\t-- setup keymaps\n\ttoggleKey = config.mapping.startStopRecording\n\tbreakPointKey = normalizeKeycodes(config.mapping.addBreakPoint)\n\tlocal icon = config.useNerdfontIcons and \" \" or \"\"\n\tlocal dapSharedIcon = config.useNerdfontIcons and \" /  \" or \"\"\n\n\tkeymap(\"n\", toggleKey, toggleRecording, { desc = icon .. \"Start/Stop Recording\" })\n\tkeymap(\"n\", config.mapping.switchSlot, switchMacroSlot, { desc = icon .. \"Switch Macro Slot\" })\n\tkeymap(\"n\", config.mapping.editMacro, editMacro, { desc = icon .. \"Edit Macro\" })\n\tkeymap(\"n\", config.mapping.yankMacro, yankMacro, { desc = icon .. \"Yank Macro\" })\n\t-- stylua: ignore\n\tkeymap(\"n\", config.mapping.deleteAllMacros, deleteAllMacros, { desc = icon .. \"Delete All Macros\" })\n\n\t-- (experimental) if true, nvim-recorder and dap will use shared keymaps:\n\t-- 1) `addBreakPoint` will map to `dap.toggle_breakpoint()` outside\n\t-- a recording. During a recording, it will add a macro breakpoint instead.\n\t-- 2) `playMacro` will map to `dap.continue()` if there is at least one\n\t-- dap-breakpoint. If there is no dap breakpoint, will play the current\n\t-- macro-slot instead\n\tdapSharedKeymaps = config.dapSharedKeymaps or false\n\tlocal breakPointDesc = dapSharedKeymaps and dapSharedIcon .. \"Breakpoint\"\n\t\tor icon .. \"Insert Macro Breakpoint.\"\n\tkeymap(\"n\", breakPointKey, addBreakPoint, { desc = breakPointDesc })\n\tlocal playDesc = dapSharedKeymaps and dapSharedIcon .. \"Continue/Play\" or icon .. \"Play Macro\"\n\tkeymap(\"n\", config.mapping.playMacro, playRecording, { desc = playDesc })\nend\n\n--------------------------------------------------------------------------------\n-- STATUS LINE COMPONENTS\n\n---returns recording status for status line plugins (e.g., used with cmdheight=0)\n---@return string\nfunction M.recordingStatus()\n\tif not isRecording() then return \"\" end\n\tlocal icon = useNerdfontIcons and \"  \" or \"\"\n\treturn icon .. \"Recording… [\" .. macroRegs[slotIndex] .. \"]\"\nend\n\n---returns non-empty for status line plugins.\n---@return string\nfunction M.displaySlots()\n\tif isRecording() then return \"\" end\n\tlocal out = {}\n\n\tfor _, reg in pairs(macroRegs) do\n\t\tlocal empty = getMacro(reg) == \"\"\n\t\tlocal active = macroRegs[slotIndex] == reg\n\t\tlocal hasBreakPoints = getMacro(reg):find(vim.pesc(breakPointKey))\n\t\tlocal bpIcon = hasBreakPoints and \"!\" or \"\"\n\n\t\tif empty and active then\n\t\t\ttable.insert(out, \"[ ]\")\n\t\telseif not empty and active then\n\t\t\ttable.insert(out, \"[\" .. reg .. bpIcon .. \"]\")\n\t\telseif not empty and not active then\n\t\t\ttable.insert(out, reg .. bpIcon)\n\t\tend\n\tend\n\n\tlocal output = table.concat(out)\n\tif output == \"[ ]\" then return \"\" end\n\tlocal icon = useNerdfontIcons and \"󰃽 \" or \"RECs \"\n\treturn icon .. output\nend\n\n--------------------------------------------------------------------------------\n\nreturn M\n"
  }
]