Full Code of chrisgrieser/nvim-recorder for AI

main 7acba6f7bbaf cached
23 files
48.3 KB
12.9k tokens
1 requests
Download .txt
Repository: chrisgrieser/nvim-recorder
Branch: main
Commit: 7acba6f7bbaf
Files: 23
Total size: 48.3 KB

Directory structure:
gitextract_zqjnb51d/

├── .editorconfig
├── .emmyrc.json
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── nvim-type-check.yml
│       ├── panvimdoc.yml
│       ├── pr-title.yml
│       ├── rumdl-lint.yml
│       ├── stale-bot.yml
│       └── stylua.yml
├── .gitignore
├── .harper-dictionary.txt
├── .luarc.jsonc
├── .rumdl.toml
├── .stylua.toml
├── LICENSE
├── README.md
├── doc/
│   └── nvim-recorder.txt
└── lua/
    └── recorder.lua

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
max_line_length = 100
end_of_line = lf
charset = utf-8
insert_final_newline = true
indent_style = tab
indent_size = 3
tab_width = 3
trim_trailing_whitespace = true

[*.{yml,yaml,scm,cff}]
indent_style = space
indent_size = 2
tab_width = 2

[*.py]
indent_style = space
indent_size = 4
tab_width = 4

[*.md]
indent_style = space
indent_size = 4
trim_trailing_whitespace = false


================================================
FILE: .emmyrc.json
================================================
{
	"runtime": {
		"version": "LuaJIT",
		"requirePattern": ["lua/?.lua", "lua/?/init.lua"]
	},
	"workspace": {
		"library": ["$VIMRUNTIME"]
	},
	"$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json"
}


================================================
FILE: .github/FUNDING.yml
================================================
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository

github: chrisgrieser


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: File a bug report
title: "[Bug]: "
labels: ["bug"]
body:
  - type: textarea
    id: bug-description
    attributes:
      label: Bug Description
      description: A clear and concise description of the bug.
    validations:
      required: true
  - type: textarea
    id: screenshot
    attributes:
      label: Relevant Screenshot
      description: If applicable, add screenshots or a screen recording to help explain your problem.
  - type: textarea
    id: reproduction-steps
    attributes:
      label: To Reproduce
      description: Steps to reproduce the problem
      placeholder: |
        For example:
        1. Go to '...'
        2. Click on '...'
        3. Scroll down to '...'
    validations:
      required: true
  - type: textarea
    id: version-info
    attributes:
      label: neovim version
      render: Text
    validations:
      required: true
  - type: checkboxes
    id: checklist
    attributes:
      label: Make sure you have done the following
      options:
        - label: I have updated to the latest version of the plugin.
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Suggest an idea
title: "Feature Request: "
labels: ["enhancement"]
body:
  - type: checkboxes
    id: checklist
    attributes:
      label: Checklist
      options:
        - label: "I have read the plugin's documentation."
          required: true
        - label: The feature would be useful to more users than just me.
          required: true
  - type: textarea
    id: feature-requested
    attributes:
      label: Feature Requested
      description: A clear and concise description of the feature.
    validations:
      required: true
  - type: textarea
    id: screenshot
    attributes:
      label: Relevant Screenshot
      description: If applicable, add screenshots or a screen recording to help explain the request.


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    commit-message:
      prefix: "chore(dependabot): "


================================================
FILE: .github/pull_request_template.md
================================================
## Problem statement
<!-- Briefly describe the issue this PR addresses. -->

## Proposed solution
<!-- Explain how this PR resolves the problem. -->

## AI usage disclosure
<!-- If you used AI beyond simple autocomplete, describe how.
AI-assisted code is not discouraged if it has been properly reviewed;
this disclosure is for transparency. -->

## Checklist
- [ ] Variable names follow `camelCase` convention.
- [ ] All AI-generated code has been reviewed by a human.
- [ ] The `README.md` has been updated for any new or modified functionality
  (the `.txt` file is auto-generated and does not need to be modified).


================================================
FILE: .github/workflows/nvim-type-check.yml
================================================
name: nvim type check

on:
  push:
    branches: [main]
    paths: ["**.lua"]
  pull_request: 
    paths: ["**.lua"]

jobs:
  build:
    name: nvim type check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: stevearc/nvim-typecheck-action@v2


================================================
FILE: .github/workflows/panvimdoc.yml
================================================
name: panvimdoc

on:
  push:
    branches: [main]
    paths:
      - README.md
      - .github/workflows/panvimdoc.yml
  workflow_dispatch: {} # allows manual execution

permissions:
  contents: write

#───────────────────────────────────────────────────────────────────────────────

jobs:
  docs:
    runs-on: ubuntu-latest
    name: README.md to vimdoc
    steps:
      - uses: actions/checkout@v6
      - run: git pull # fix failure when multiple commits are pushed in succession
      - run: mkdir -p doc

      - name: panvimdoc
        uses: kdheepak/panvimdoc@main
        with:
          vimdoc: ${{ github.event.repository.name }}
          version: "Neovim"
          demojify: true
          treesitter: true

      - run: git pull
      - name: push changes
        uses: stefanzweifel/git-auto-commit-action@v7
        with:
          commit_message: "chore: auto-generate vimdocs"
          branch: ${{ github.head_ref }}


================================================
FILE: .github/workflows/pr-title.yml
================================================
name: PR title

on:
  pull_request_target:
    types:
      - opened
      - edited
      - synchronize
      - reopened
      - ready_for_review

permissions:
  pull-requests: read

jobs:
  semantic-pull-request:
    name: Check PR title
    runs-on: ubuntu-latest
    steps:
      - uses: amannn/action-semantic-pull-request@v6
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          requireScope: false
          subjectPattern: ^(?![A-Z]).+$ # disallow title starting with capital
          types: | # add `improv` to the list of allowed types
            improv
            fix
            feat
            refactor
            build
            ci
            style
            test
            chore
            perf
            docs
            break
            revert


================================================
FILE: .github/workflows/rumdl-lint.yml
================================================
name: Markdown linting via rumdl

on:
  push:
    branches: [main]
    paths:
      - "**/*.md"
      - ".github/workflows/rumdl-lint.yml"
      - ".rumdl.toml"
  pull_request:
    paths:
      - "**/*.md"

jobs:
  rumdl:
    name: rumdl
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: rvben/rumdl@v0
        with:
          report-type: annotations


================================================
FILE: .github/workflows/stale-bot.yml
================================================
name: Stale bot
on:
  schedule:
    - cron: "18 04 * * 3" 

permissions:
  issues: write
  pull-requests: write

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
      - name: Close stale issues
        uses: actions/stale@v10
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

          # DOCS https://github.com/actions/stale#all-options
          days-before-stale: 180
          days-before-close: 7
          stale-issue-label: "Stale"
          stale-issue-message: |
            This issue has been automatically marked as stale.
            **If this issue is still affecting you, please leave any comment**, for example "bump", and it will be kept open.
          close-issue-message: |
            This issue has been closed due to inactivity, and will not be monitored.


================================================
FILE: .github/workflows/stylua.yml
================================================
name: Stylua check

on:
  push:
    branches: [main]
    paths: ["**.lua"]
  pull_request:
    paths: ["**.lua"]

jobs:
  stylua:
    name: Stylua
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: JohnnyMorganz/stylua-action@v5
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          version: latest
          args: --check .


================================================
FILE: .gitignore
================================================
# help-tags auto-generated by lazy.nvim
doc/tags


================================================
FILE: .harper-dictionary.txt
================================================
DAP


================================================
FILE: .luarc.jsonc
================================================
{
	"runtime.version": "LuaJIT",

	"workspace.library": ["$VIMRUNTIME/lua"], // nvim-lua runtime

	"diagnostics": {
		"unusedLocalExclude": ["_*"], // allow `_varname` for unused variables
		"groupFileStatus": {
			"luadoc": "Any", // require stricter annotations
			"conventions": "Any" // disallow global variables
		}
	},

	"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json"
}


================================================
FILE: .rumdl.toml
================================================
# DOCS https://github.com/rvben/rumdl/blob/main/docs/global-settings.md

[global]
line-length = 80
disable = ["blanks-around-lists"]  # rule of proximity

# ------------------------------------------------------------------------------

[ul-style]
style = "dash"  # GitHub default & quicker to type

[ul-indent]
indent = 4  # consistent with .editorconfig

[line-length]
code-blocks = false
reflow = true  # enable auto-formatting

[blanks-around-headings]
lines-below = 0  # rule of proximity

[ol-prefix]
style = "ordered"

[no-inline-html]
allowed-elements = ["a", "img", "kbd"]  # badges

[emphasis-style]
style = "asterisk"  # better than underscore, since not considered a word-char

[strong-style]
style = "asterisk"

[table-format]
enabled = true  # opt-in rule

[heading-capitalization]
enabled = true  # opt-in rule
style = "sentence_case"
ignore-words = ["nvim", "Obsidian", "Alfred"]

[toc-validation]
enabled = true  # opt-in rule

# ------------------------------------------------------------------------------

[per-file-ignores]
".github/pull_request_template.md" = ["first-line-h1"]


================================================
FILE: .stylua.toml
================================================
# https://github.com/JohnnyMorganz/StyLua#options
#───────────────────────────────────────────────────────────────────────────────
syntax = "LuaJIT"  # needed to support `::labels::`
column_width = 100
line_endings = "Unix"
indent_type = "Tabs"
indent_width = 3
quote_style = "AutoPreferDouble"
call_parentheses = "NoSingleTable"
collapse_simple_statement = "Always"
sort_requires.enabled = true


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 Christopher Grieser

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Nvim-recorder 📹 <!-- rumdl-disable-line MD063 -->
<a href="https://dotfyle.com/plugins/chrisgrieser/nvim-recorder">
<img alt="badge" src="https://dotfyle.com/plugins/chrisgrieser/nvim-recorder/shield"/></a>

Enhance the usage of macros in Neovim.

<!-- toc -->
- [Features](#features)
- [Setup](#setup)
    - [Installation](#installation)
    - [Configuration](#configuration)
    - [Status line components](#status-line-components)
- [Basic usage](#basic-usage)
- [Advanced usage](#advanced-usage)
    - [Performance optimizations](#performance-optimizations)
    - [Macro breakpoints](#macro-breakpoints)
    - [Lazy-loading the plugin](#lazy-loading-the-plugin)
- [About the developer](#about-the-developer)
<!-- tocstop -->

## Features
- **Simplified controls**: One key to start and stop recording, a second key for
  playing the macro. Instead of `qa … q @a @@`, you just do `q … q Q Q`.[^1]
- **Macro Breakpoints** for easier debugging of macros. Breakpoints can also be
  set after the recording and are automatically ignored when triggering a macro
  with a count.
- **Status line components**: Particularly useful if you use `cmdheight=0` where
  the recording status is not visible.
- **Macro-to-Mapping**: Copy a macro, so you can save it as a mapping.
- **Various quality-of-life features**: notifications with macro content, the
  ability to cancel a recording, a command to edit macros,
- **Performance Optimizations for large macros**: When the macro is triggered
  with a high count, temporarily enable some performance improvements.
- Uses up-to-date nvim features like `vim.notify`. This means you can get
  confirmation notices with plugins like
  [nvim-notify](https://github.com/rcarriga/nvim-notify).

## Setup

### Installation

```lua
-- lazy.nvim
{
	"chrisgrieser/nvim-recorder",
	dependencies = "rcarriga/nvim-notify", -- optional
	opts = {}, -- required even with default settings, since it calls `setup()`
},

-- packer
use {
	"chrisgrieser/nvim-recorder",
	requires = "rcarriga/nvim-notify", -- optional
	config = function() require("recorder").setup() end,
}
```

Calling `setup()` (or `lazy`'s `opts`) is **required**.

### Configuration

```lua
-- default values
require("recorder").setup {
	-- Named registers where macros are saved (single lowercase letters only).
	-- The first register is the default register used as macro-slot after
	-- startup.
	slots = { "a", "b" },

    -- specify one of options: 
    -- [static]   -> use static slots, this is default behaviour
    -- [rotate]   -> rotates through letters specified in slots[]
    dynamicSlots = "static",

	mapping = {
		startStopRecording = "q",
		playMacro = "Q",
		switchSlot = "<C-q>",
		editMacro = "cq",
		deleteAllMacros = "dq",
		yankMacro = "yq",
		-- ⚠️ this should be a string you don't use in insert mode during a macro
		addBreakPoint = "##",
	},

	-- Clears all macros-slots on startup.
	clear = false,

	-- Log level used for non-critical notifications; mostly relevant for nvim-notify.
	-- (Note that by default, nvim-notify does not show the levels `trace` & `debug`.)
	logLevel = vim.log.levels.INFO, -- :help vim.log.levels

	-- If enabled, only essential notifications are sent.
	-- If you do not use a plugin like nvim-notify, set this to `true`
	-- to remove otherwise annoying messages.
	lessNotifications = false,

	-- Use nerdfont icons in the status bar components and keymap descriptions
	useNerdfontIcons = true,

	-- Performance optimizations for macros with high count. When `playMacro` is
	-- triggered with a count higher than the threshold, nvim-recorder
	-- temporarily changes changes some settings for the duration of the macro.
	performanceOpts = {
		countThreshold = 100,
		lazyredraw = true, -- enable lazyredraw (see `:h lazyredraw`)
		noSystemClipboard = true, -- remove `+`/`*` from clipboard option
		autocmdEventsIgnore = { -- temporarily ignore these autocmd events
			"TextChangedI",
			"TextChanged",
			"InsertLeave",
			"InsertEnter",
			"InsertCharPre",
		},
	},

	-- [experimental] partially share keymaps with nvim-dap.
	-- (See README for further explanations.)
	dapSharedKeymaps = false,
}
```

If you want to handle multiple macros or use `cmdheight=0`, it is recommended to
also set up the status line components:

### Status line components

```lua
-- Indicates whether you are currently recording. Useful if you are using
-- `cmdheight=0`, where recording-status is not visible.
require("recorder").recordingStatus()

-- Displays non-empty macro-slots (registers) and indicates the selected ones.
-- Only displayed when *not* recording. Slots with breakpoints get an extra `#`.
require("recorder").displaySlots()
```

> [!TIP]
> Use with the config `clear = true` to see recordings you made this session.

Example for adding the status line components to [lualine](https://github.com/nvim-lualine/lualine.nvim):

```lua
lualine_y = {
	{ require("recorder").displaySlots },
},
lualine_z = {
	{ require("recorder").recordingStatus },
},
```

> [!TIP]
> Put the components in different status line segments, so they have
> a different color, making the recording status more distinguishable
> from saved recordings

## Basic usage
- `startStopRecording`: Starts recording to the current macro slot (so you do
  not need to specify a register). Press again to end the recording.
- `playMacro`: Plays the macro in the current slot (without the need to specify
  a register).
- `switchSlot`: Cycles through the registers you specified in the configuration.
  Also show a notification with the slot and its content. (The currently
  selected slot can be seen in the [status line
  component](#status-line-components).)
- `editMacro`: Edit the macro recorded in the active slot. (Be aware that these
  are the keystrokes in "encoded" form.)
- `yankMacro`: Copies the current macro in decoded form that can be used to
  create a mapping from it. Breakpoints are removed from the copied macro.
- `deleteAllMacros`: Copies the current macro in decoded form that can be used
  to

> [!TIP]
> For recursive macros (playing a macro inside a macro), you can still use
> the default command `@a`.

## Advanced usage

### Performance optimizations
Running macros with a high count can be demanding on the system and result in
lags. For this reason, `nvim-recorder` provides some performance optimizations
that are temporarily enabled when a macro with a high count is run.

Note that these optimizations do have some potential drawbacks.
- [`lazyredraw`](https://neovim.io/doc/user/options.html#'lazyredraw') disables
  redrawing of the screen, which makes it harder to notice edge cases not
  considered in the macro. It may also appear as if the screen is frozen for a
  while.
- Disabling the system clipboard is mostly safe, if you do not intend to copy
  content to it with the macro.
- Ignoring auto-commands is not recommended, when you rely on certain plugin
  functionality during the macro, since it can potentially disrupt those
  plugins' effect.

### Macro breakpoints
`nvim-recorder` allows you to set breakpoints in your macros which can be
helpful for debugging macros. Breakpoints are automatically ignored when you
trigger the macro with a count.

**Setting Breakpoints** <!-- rumdl-disable-line MD036 -->
- *During a recording:* press the `addBreakPoint` key (default: `##`) in normal
   mode
- *After a recording:* use `editMacro` and add or remove the `##` manually.

**Playing Macros with Breakpoints** <!-- rumdl-disable-line MD036 -->
- Using the `playMacro` key, the macro automatically stops at the next
  breakpoint. The next time you press `playMacro`, the next segment of the macro
  is played.
- Starting a new recording, editing a macro, yanking a macro, or switching macro
  slot all reset the sequence, meaning that `playMacro` starts from the
  beginning again.

> [!TIP]
> You can do other things in between playing segments of the macro, like
> moving a few characters to the left or right. That way you can also use
> breakpoints to manually correct irregularities.

**Ignoring Breakpoints** <!-- rumdl-disable-line MD036 -->
When you play the macro with a *count* (for example `50Q`), breakpoints are
automatically ignored.

> [!TIP]
> Add a count of 1 (`1Q`) to play a macro once and still ignore breakpoints.

**Shared Keybindings with `nvim-dap`**  
If you are using [nvim-dap](https://github.com/mfussenegger/nvim-dap), you can
use `dapSharedKeymaps = true` to set up the following shared keybindings:
1. `addBreakPoint` maps to `dap.toggle_breakpoint()` outside
a recording. During a recording, it adds a macro breakpoint instead.
2. `playMacro` maps to `dap.continue()` if there is at least one
DAP-breakpoint. If there is no DAP-breakpoint, plays the current
macro-slot instead.

Note that this feature is experimental, since the [respective API from nvim-dap
is non-public and can be changed without deprecation
notice](https://github.com/mfussenegger/nvim-dap/discussions/810#discussioncomment-4623606).

### Lazy-loading the plugin
`nvim-recorder` is best lazy-loaded on the mappings for `startStopRecording` and
`playMacro`. However, adding the status line components to `lualine` will cause
the plugin to load before you start or play a recording.

To avoid this, the status line components need to be loaded only in the plugin's
`config`. The drawback of this method is that no component is shown when until
you start or play a recording (which you can completely disregard when you set
`clear = true`, though).

Nonetheless, the plugin is pretty lightweight (~400 lines of code), so not
lazy-loading it should not have a big impact.

```lua
-- minimal config for lazy-loading with lazy.nvim
{
	"chrisgrieser/nvim-recorder",
	dependencies = "rcarriga/nvim-notify",
	keys = {
		-- these must match the keys in the mapping config below
		{ "q", desc = " Start Recording" },
		{ "Q", desc = " Play Recording" },
	},
	config = function()
		require("recorder").setup({
			mapping = {
				startStopRecording = "q",
				playMacro = "Q",
			},
		})

			local lualineZ = require("lualine").get_config().sections.lualine_z or {}
			local lualineY = require("lualine").get_config().sections.lualine_y or {}
			table.insert(lualineZ, { require("recorder").recordingStatus })
			table.insert(lualineY, { require("recorder").displaySlots })

			require("lualine").setup {
				tabline = {
					lualine_y = lualineY,
					lualine_z = lualineZ,
				},
			}
	end,
},
```

## About the developer
In my day job, I am a sociologist studying the social mechanisms underlying the
digital economy. For my PhD project, I investigate the governance of the app
economy and how software ecosystems manage the tension between innovation and
compatibility. If you are interested in this subject, feel free to get in touch.

- [Website](https://chris-grieser.de/)
- [Mastodon](https://pkm.social/@pseudometa)
- [ResearchGate](https://www.researchgate.net/profile/Christopher-Grieser)
- [LinkedIn](https://www.linkedin.com/in/christopher-grieser-ba693b17a/)

If you find this project helpful, you can support me via [🩷 GitHub
Sponsors](https://github.com/sponsors/chrisgrieser?frequency=one-time).

[^1]: As opposed to vim, Neovim already allows you to use `Q` to [play the last
    recorded macro](https://neovim.io/doc/user/repeat.html#Q). Considering this,
    the simplified controls really only save you one keystroke for one-off
    macros. However, as opposed to Neovim's built-in controls, you can still
    keep using `Q` for playing the not-most-recently recorded macro.


================================================
FILE: doc/nvim-recorder.txt
================================================
*nvim-recorder.txt*          For Neovim          Last change: 2026 February 06

==============================================================================
Table of Contents                            *nvim-recorder-table-of-contents*

1. Nvim-recorder                                |nvim-recorder-nvim-recorder-|
  - Features                           |nvim-recorder-nvim-recorder--features|
  - Setup                                 |nvim-recorder-nvim-recorder--setup|
  - Basic usage                     |nvim-recorder-nvim-recorder--basic-usage|
  - Advanced usage               |nvim-recorder-nvim-recorder--advanced-usage|
  - About the developer     |nvim-recorder-nvim-recorder--about-the-developer|

==============================================================================
1. Nvim-recorder                                *nvim-recorder-nvim-recorder-*



Enhance the usage of macros in Neovim.

- |nvim-recorder-features|
- |nvim-recorder-setup|
    - |nvim-recorder-installation|
    - |nvim-recorder-configuration|
    - |nvim-recorder-status-line-components|
- |nvim-recorder-basic-usage|
- |nvim-recorder-advanced-usage|
    - |nvim-recorder-performance-optimizations|
    - |nvim-recorder-macro-breakpoints|
    - |nvim-recorder-lazy-loading-the-plugin|
- |nvim-recorder-about-the-developer|


FEATURES                               *nvim-recorder-nvim-recorder--features*

- **Simplified controls**: One key to start and stop recording, a second key for
    playing the macro. Instead of `qa … q @a @@`, you just do `q … q Q Q`.
- **Macro Breakpoints** for easier debugging of macros. Breakpoints can also be
    set after the recording and are automatically ignored when triggering a macro
    with a count.
- **Status line components**: Particularly useful if you use `cmdheight=0` where
    the recording status is not visible.
- **Macro-to-Mapping**: Copy a macro, so you can save it as a mapping.
- **Various quality-of-life features**: notifications with macro content, the
    ability to cancel a recording, a command to edit macros,
- **Performance Optimizations for large macros**: When the macro is triggered
    with a high count, temporarily enable some performance improvements.
- Uses up-to-date nvim features like `vim.notify`. This means you can get
    confirmation notices with plugins like
    nvim-notify <https://github.com/rcarriga/nvim-notify>.


SETUP                                     *nvim-recorder-nvim-recorder--setup*


INSTALLATION ~

>lua
    -- lazy.nvim
    {
        "chrisgrieser/nvim-recorder",
        dependencies = "rcarriga/nvim-notify", -- optional
        opts = {}, -- required even with default settings, since it calls `setup()`
    },
    
    -- packer
    use {
        "chrisgrieser/nvim-recorder",
        requires = "rcarriga/nvim-notify", -- optional
        config = function() require("recorder").setup() end,
    }
<

Calling `setup()` (or `lazy`’s `opts`) is **required**.


CONFIGURATION ~

>lua
    -- default values
    require("recorder").setup {
        -- Named registers where macros are saved (single lowercase letters only).
        -- The first register is the default register used as macro-slot after
        -- startup.
        slots = { "a", "b" },
    
        -- specify one of options: 
        -- [static]   -> use static slots, this is default behaviour
        -- [rotate]   -> rotates through letters specified in slots[]
        dynamicSlots = "static",
    
        mapping = {
            startStopRecording = "q",
            playMacro = "Q",
            switchSlot = "<C-q>",
            editMacro = "cq",
            deleteAllMacros = "dq",
            yankMacro = "yq",
            -- ⚠️ this should be a string you don't use in insert mode during a macro
            addBreakPoint = "##",
        },
    
        -- Clears all macros-slots on startup.
        clear = false,
    
        -- Log level used for non-critical notifications; mostly relevant for nvim-notify.
        -- (Note that by default, nvim-notify does not show the levels `trace` & `debug`.)
        logLevel = vim.log.levels.INFO, -- :help vim.log.levels
    
        -- If enabled, only essential notifications are sent.
        -- If you do not use a plugin like nvim-notify, set this to `true`
        -- to remove otherwise annoying messages.
        lessNotifications = false,
    
        -- Use nerdfont icons in the status bar components and keymap descriptions
        useNerdfontIcons = true,
    
        -- Performance optimizations for macros with high count. When `playMacro` is
        -- triggered with a count higher than the threshold, nvim-recorder
        -- temporarily changes changes some settings for the duration of the macro.
        performanceOpts = {
            countThreshold = 100,
            lazyredraw = true, -- enable lazyredraw (see `:h lazyredraw`)
            noSystemClipboard = true, -- remove `+`/`*` from clipboard option
            autocmdEventsIgnore = { -- temporarily ignore these autocmd events
                "TextChangedI",
                "TextChanged",
                "InsertLeave",
                "InsertEnter",
                "InsertCharPre",
            },
        },
    
        -- [experimental] partially share keymaps with nvim-dap.
        -- (See README for further explanations.)
        dapSharedKeymaps = false,
    }
<

If you want to handle multiple macros or use `cmdheight=0`, it is recommended
to also set up the status line components:


STATUS LINE COMPONENTS ~

>lua
    -- Indicates whether you are currently recording. Useful if you are using
    -- `cmdheight=0`, where recording-status is not visible.
    require("recorder").recordingStatus()
    
    -- Displays non-empty macro-slots (registers) and indicates the selected ones.
    -- Only displayed when *not* recording. Slots with breakpoints get an extra `#`.
    require("recorder").displaySlots()
<


  [!TIP] Use with the config `clear = true` to see recordings you made this
  session.
Example for adding the status line components to lualine
<https://github.com/nvim-lualine/lualine.nvim>:

>lua
    lualine_y = {
        { require("recorder").displaySlots },
    },
    lualine_z = {
        { require("recorder").recordingStatus },
    },
<


  [!TIP] Put the components in different status line segments, so they have a
  different color, making the recording status more distinguishable from saved
  recordings

BASIC USAGE                         *nvim-recorder-nvim-recorder--basic-usage*

- `startStopRecording`: Starts recording to the current macro slot (so you do
    not need to specify a register). Press again to end the recording.
- `playMacro`: Plays the macro in the current slot (without the need to specify
    a register).
- `switchSlot`: Cycles through the registers you specified in the configuration.
    Also show a notification with the slot and its content. (The currently
    selected slot can be seen in the |nvim-recorder-status-line-component|.)
- `editMacro`: Edit the macro recorded in the active slot. (Be aware that these
    are the keystrokes in "encoded" form.)
- `yankMacro`: Copies the current macro in decoded form that can be used to
    create a mapping from it. Breakpoints are removed from the copied macro.
- `deleteAllMacros`: Copies the current macro in decoded form that can be used
    to


  [!TIP] For recursive macros (playing a macro inside a macro), you can still use
  the default command `@a`.

ADVANCED USAGE                   *nvim-recorder-nvim-recorder--advanced-usage*


PERFORMANCE OPTIMIZATIONS ~

Running macros with a high count can be demanding on the system and result in
lags. For this reason, `nvim-recorder` provides some performance optimizations
that are temporarily enabled when a macro with a high count is run.

Note that these optimizations do have some potential drawbacks. -
|`lazyredraw`| disables redrawing of the screen, which makes it harder to
notice edge cases not considered in the macro. It may also appear as if the
screen is frozen for a while. - Disabling the system clipboard is mostly safe,
if you do not intend to copy content to it with the macro. - Ignoring
auto-commands is not recommended, when you rely on certain plugin functionality
during the macro, since it can potentially disrupt those plugins’ effect.


MACRO BREAKPOINTS ~

`nvim-recorder` allows you to set breakpoints in your macros which can be
helpful for debugging macros. Breakpoints are automatically ignored when you
trigger the macro with a count.

**Setting Breakpoints** - _During a recording:_ press the `addBreakPoint` key
(default: `##`) in normal mode - _After a recording:_ use `editMacro` and add
or remove the `##` manually.

**Playing Macros with Breakpoints** - Using the `playMacro` key, the macro
automatically stops at the next breakpoint. The next time you press
`playMacro`, the next segment of the macro is played. - Starting a new
recording, editing a macro, yanking a macro, or switching macro slot all reset
the sequence, meaning that `playMacro` starts from the beginning again.


  [!TIP] You can do other things in between playing segments of the macro, like
  moving a few characters to the left or right. That way you can also use
  breakpoints to manually correct irregularities.
**Ignoring Breakpoints** When you play the macro with a _count_ (for example
`50Q`), breakpoints are automatically ignored.


  [!TIP] Add a count of 1 (`1Q`) to play a macro once and still ignore
  breakpoints.
**Shared Keybindings with nvim-dap** If you are using nvim-dap
<https://github.com/mfussenegger/nvim-dap>, you can use `dapSharedKeymaps =
true` to set up the following shared keybindings: 1. `addBreakPoint` maps to
`dap.toggle_breakpoint()` outside a recording. During a recording, it adds a
macro breakpoint instead. 2. `playMacro` maps to `dap.continue()` if there is
at least one DAP-breakpoint. If there is no DAP-breakpoint, plays the current
macro-slot instead.

Note that this feature is experimental, since the respective API from nvim-dap
is non-public and can be changed without deprecation notice
<https://github.com/mfussenegger/nvim-dap/discussions/810#discussioncomment-4623606>.


LAZY-LOADING THE PLUGIN ~

`nvim-recorder` is best lazy-loaded on the mappings for `startStopRecording`
and `playMacro`. However, adding the status line components to `lualine` will
cause the plugin to load before you start or play a recording.

To avoid this, the status line components need to be loaded only in the
plugin’s `config`. The drawback of this method is that no component is shown
when until you start or play a recording (which you can completely disregard
when you set `clear = true`, though).

Nonetheless, the plugin is pretty lightweight (~400 lines of code), so not
lazy-loading it should not have a big impact.

>lua
    -- minimal config for lazy-loading with lazy.nvim
    {
        "chrisgrieser/nvim-recorder",
        dependencies = "rcarriga/nvim-notify",
        keys = {
            -- these must match the keys in the mapping config below
            { "q", desc = " Start Recording" },
            { "Q", desc = " Play Recording" },
        },
        config = function()
            require("recorder").setup({
                mapping = {
                    startStopRecording = "q",
                    playMacro = "Q",
                },
            })
    
                local lualineZ = require("lualine").get_config().sections.lualine_z or {}
                local lualineY = require("lualine").get_config().sections.lualine_y or {}
                table.insert(lualineZ, { require("recorder").recordingStatus })
                table.insert(lualineY, { require("recorder").displaySlots })
    
                require("lualine").setup {
                    tabline = {
                        lualine_y = lualineY,
                        lualine_z = lualineZ,
                    },
                }
        end,
    },
<


ABOUT THE DEVELOPER         *nvim-recorder-nvim-recorder--about-the-developer*

In my day job, I am a sociologist studying the social mechanisms underlying the
digital economy. For my PhD project, I investigate the governance of the app
economy and how software ecosystems manage the tension between innovation and
compatibility. If you are interested in this subject, feel free to get in
touch.

- Website <https://chris-grieser.de/>
- Mastodon <https://pkm.social/@pseudometa>
- ResearchGate <https://www.researchgate.net/profile/Christopher-Grieser>
- LinkedIn <https://www.linkedin.com/in/christopher-grieser-ba693b17a/>

If you find this project helpful, you can support me via GitHub Sponsors
<https://github.com/sponsors/chrisgrieser?frequency=one-time>.

Generated by panvimdoc <https://github.com/kdheepak/panvimdoc>

vim:tw=78:ts=8:noet:ft=help:norl:


================================================
FILE: lua/recorder.lua
================================================
local M = {}

local fn = vim.fn
local v = vim.v
local opt = vim.opt
local keymap = vim.keymap.set

-- internal vars
local config, macroRegs, slotIndex, defaultLogLevel, breakCounter, firstRun

-- Use this function to normalize keycodes (which can have multiple
-- representations, e.g. <C-f> or <C-F>).
---@param mapping string
local normalizeKeycodes = function(mapping)
	return fn.keytrans(vim.api.nvim_replace_termcodes(mapping, true, true, true))
end

local getMacro = function(reg)
	-- Some keys (e.g. <C-F>) have different representations when they are recorded
	-- versus when they are a result of vim.api.nvim_replace_termcodes (for example).
	-- This ensures that whenever we are manually doing something with register contents,
	-- they are always consistent.
	return vim.api.nvim_replace_termcodes(fn.keytrans(vim.fn.getreg(reg)), true, true, true)
end
local setMacro = function(reg, recording) vim.fn.setreg(reg, recording, "c") end

-- vars which can be set by the user
local toggleKey, breakPointKey, dapSharedKeymaps, lessNotifications, useNerdfontIcons
local perf = {}

--------------------------------------------------------------------------------

---@param msg string
---@param level? 0|1|2|3|4 vim.log.levels
---@param importance "essential"|"nonessential"
---@param extraOpts? table
local function notify(msg, importance, level, extraOpts)
	if importance == "nonessential" and lessNotifications then return end
	if not level then level = defaultLogLevel end
	local opts = vim.tbl_deep_extend("force", { title = "nvim-recorder" }, extraOpts or {})
	vim.notify(msg, level, opts)
end

---@return boolean
local function isRecording() return fn.reg_recording() ~= "" end

---@return boolean
local function isPlaying() return fn.reg_executing() ~= "" end

---runs `:normal` natively with bang
---@param cmdStr string
local function normal(cmdStr) vim.cmd.normal { cmdStr, bang = true } end

--------------------------------------------------------------------------------
-- COMMANDS

-- start/stop recording macro into the current slot
local function toggleRecording()
	if config.dynamicSlots == "rotate" and not firstRun and not isRecording() then
		slotIndex = slotIndex + 1
		if slotIndex > #macroRegs then slotIndex = 1 end
	end
	if firstRun then firstRun = false end
	local reg = macroRegs[slotIndex]

	-- start recording
	if not isRecording() then
		breakCounter = 0 -- reset break points
		normal("q" .. reg)
		notify("Recording to [" .. reg .. "]…", "essential")
		return
	end

	-- stop recording
	local prevRec = getMacro(macroRegs[slotIndex])
	normal("q")

	-- NOTE the macro key records itself, so it has to be removed from the
	-- register. As this function has to know the variable length of the
	-- LHS key that triggered it, it has to be passed in via .setup()-function
	local decodedToggleKey = vim.api.nvim_replace_termcodes(toggleKey, true, true, true)
	local recording = getMacro(reg):sub(1, -1 * (#decodedToggleKey + 1))
	setMacro(reg, recording)

	local justRecorded = fn.keytrans(getMacro(reg))
	if justRecorded == "" then
		if config.dynamicSlots == "rotate" then slotIndex = slotIndex - 1 end
		setMacro(reg, prevRec)
		notify("Recording aborted.\n(Previous recording is kept.)", "essential")
	elseif not lessNotifications then
		notify("Recorded [" .. reg .. "]:\n" .. justRecorded, "nonessential")
	end
end

---play the macro recorded in current slot
local function playRecording()
	local reg = macroRegs[slotIndex]
	local macro = getMacro(reg)

	-- Guard Clause 1: Toggle Breakpoint instead of Macro
	-- WARN undocumented and prone to change https://github.com/mfussenegger/nvim-dap/discussions/810#discussioncomment-4623606
	if dapSharedKeymaps then
		-- nested to avoid requiring `dap` for lazyloading
		local dapBreakpointsExist = next(require("dap.breakpoints").get()) ~= nil
		if dapBreakpointsExist then
			require("dap").continue()
			return
		end
	end

	-- Guard Clause 2: Recursively play macro
	if isRecording() then
		-- stylua: ignore
		notify(
			"Playing the macro while it is recording would cause recursion problems. Aborting.\n" ..
			"(You can still use recursive macros by using `@" .. reg .. "`)",
			"essential",
			vim.log.levels.ERROR
		)
		normal("q") -- end recording
		setMacro(reg, "") -- empties macro since the recursion has been recorded there
		return
	end

	-- Guard Clause 3: Slot is empty
	if macro == "" then
		notify("Macro Slot [" .. reg .. "] is empty.", "essential", vim.log.levels.WARN)
		return
	end

	-- EXECUTE MACRO
	local countGiven = v.count ~= 0
	local hasBreakPoints = fn.keytrans(macro):find(vim.pesc(breakPointKey))
	local usePerfOptimizations = v.count1 >= perf.countThreshold

	-- macro (w/ breakpoints)
	if hasBreakPoints and not countGiven then
		breakCounter = breakCounter + 1

		local macroParts = {}
		for _, macroPart in ipairs(vim.split(fn.keytrans(macro), vim.pesc(breakPointKey), {})) do
			table.insert(macroParts, vim.api.nvim_replace_termcodes(macroPart, true, true, true))
		end

		local partialMacro = macroParts[breakCounter]

		setMacro(reg, partialMacro)
		normal("@" .. reg)
		setMacro(reg, macro) -- restore original macro for all other purposes like prewing slots

		if breakCounter ~= #macroParts then
			notify("Reached Breakpoint #" .. tostring(breakCounter), "essential")
		else
			notify("Reached end of macro", "essential")
			breakCounter = 0
		end

	-- macro (w/ perf optimizations)
	elseif usePerfOptimizations then
		-- message to avoid confusion by the user due to performance optimizations
		local msg = "Running macro with performance optimizations…"
		if perf.lazyredraw then
			msg = msg
				.. "\nnvim might appear to freeze due to lazy redrawing. \nThis is to be expected and not a bug."
		end
		notify(msg, "nonessential", nil, { animate = false }) -- no animation as macro will be blocking

		local original = {}
		if perf.lazyredraw then
			original.lazyredraw = opt.lazyredraw:get() ---@diagnostic disable-line: undefined-field
			opt.lazyredraw = true
		end
		if perf.noSystemclipboard then
			original.clipboard = opt.clipboard:get() ---@diagnostic disable-line: undefined-field
			opt.clipboard = ""
		end
		original.eventignore = opt.eventignore:get()
		opt.eventignore = perf.autocmdEventsIgnore

		-- if notification is shown, defer to ensure it is displayed
		local count = v.count1 -- counts needs to be saved due to scoping by defer_fn
		vim.defer_fn(function()
			normal(count .. "@" .. reg)

			if perf.lazyredraw then vim.opt.lazyredraw = original.lazyredraw end
			if perf.noSystemclipboard then opt.clipboard = original.clipboard end
			opt.eventignore = original.eventignore
		end, 500)

	-- macro (regular)
	else
		normal(v.count1 .. "@" .. reg)
	end
end

---changes the active slot
local function switchMacroSlot()
	slotIndex = slotIndex + 1
	breakCounter = 0 -- reset breakpoint counter

	if slotIndex > #macroRegs then slotIndex = 1 end
	local reg = macroRegs[slotIndex]
	local currentMacro = fn.keytrans(getMacro(reg))
	local msg = " Now using macro slot [" .. reg .. "]"
	if currentMacro ~= "" then
		msg = msg .. ".\n" .. currentMacro
	else
		msg = msg .. "\n(empty)"
	end
	notify(msg, "nonessential")
end

---edit the current slot
local function editMacro()
	breakCounter = 0 -- reset breakpoint counter
	local reg = macroRegs[slotIndex]
	local macroContent = fn.keytrans(getMacro(reg))
	local inputConfig = {
		prompt = "Edit Macro [" .. reg .. "]:",
		default = macroContent,
	}
	vim.ui.input(inputConfig, function(editedMacro)
		if not editedMacro then return end -- cancellation
		setMacro(reg, vim.api.nvim_replace_termcodes(editedMacro, true, true, true))
		notify("Edited Macro [" .. reg .. "]:\n" .. editedMacro, "nonessential")
	end)
end

---@param mode? "silent"
local function deleteAllMacros(mode)
	breakCounter = 0 -- reset breakpoint counter
	for _, reg in pairs(macroRegs) do
		setMacro(reg, "")
	end
	if mode ~= "silent" then notify("All macros deleted.", "nonessential") end
end

local function yankMacro()
	breakCounter = 0
	local reg = macroRegs[slotIndex]
	local macroContent = fn.keytrans(getMacro(reg))
	if macroContent == "" then
		notify(
			"Nothing to copy, macro slot [" .. reg .. "] is still empty.",
			"essential",
			vim.log.levels.WARN
		)
		return
	end
	-- remove breakpoints when yanking the macro
	macroContent = macroContent:gsub(vim.pesc(breakPointKey), "")

	local clipboardOpt = opt.clipboard:get() ---@diagnostic disable-line: undefined-field
	local useSystemClipb = #clipboardOpt > 0 and clipboardOpt[1]:find("unnamed")
	local copyToReg = useSystemClipb and "+" or '"'

	fn.setreg(copyToReg, macroContent)
	notify("Copied Macro [" .. reg .. "]:\n" .. macroContent, "nonessential")
end

local function addBreakPoint()
	if isRecording() then
		-- INFO nothing happens, but the key is still recorded in the macro
		notify("Macro breakpoint added.", "essential")
	elseif not isPlaying() and not dapSharedKeymaps then
		notify("Cannot insert breakpoint outside of a recording.", "essential", vim.log.levels.WARN)
	elseif not isPlaying() and dapSharedKeymaps then
		-- only test for dap here to not interfere with user lazyloading
		if require("dap") then require("dap").toggle_breakpoint() end
	end
end

--------------------------------------------------------------------------------
-- CONFIG

---@class configObj
---@field slots string[] named register slots
---@field dynamicSlots string 2 states we could choose from:
---static   -> use static slots
---rotate   -> through letters specified in slots[] if end is encountered it goes(overwrite) from start
---@field clear boolean whether to clear slots/registers on setup
---@field timeout number Default timeout for notification
---@field mapping maps individual mappings
---@field logLevel integer log level (vim.log.levels)
---@field lessNotifications boolean plugin is less verbose, shows only essential or critical notifications
---@field performanceOpts perfOpts various performance options
---@field dapSharedKeymaps boolean (experimental) partially share keymaps with dap
---@field useNerdfontIcons boolean currently only relevant for status bar components

---@class perfOpts
---@field countThreshold number if count used is higher than threshold, the following performance optimizations are applied
---@field lazyredraw boolean :h lazyredraw
---@field noSystemClipboard boolean no `*` or `+` in clipboard https://vi.stackexchange.com/a/31888
---@field autocmdEventsIgnore string[] list of autocmd events to ignore

---@class maps
---@field startStopRecording string
---@field playMacro string
---@field editMacro string
---@field yankMacro string
---@field deleteAllMacros string
---@field switchSlot string
---@field addBreakPoint string

---@param userConfig configObj
function M.setup(userConfig)
	-- initialize values on plugin load
	slotIndex = 1
	breakCounter = 0
	firstRun = true

	local defaultConfig = {
		slots = { "a", "b" },
		dynamicSlots = "static",
		mapping = {
			startStopRecording = "q",
			playMacro = "Q",
			switchSlot = "<C-q>",
			editMacro = "cq",
			deleteAllMacros = "dq",
			yankMacro = "yq",
			addBreakPoint = "##",
		},
		dapSharedKeymaps = false,
		clear = false,
		logLevel = vim.log.levels.INFO,
		lessNotifications = false,
		useNerdfontIcons = true,
		performanceOpts = {
			countThreshold = 100,
			lazyredraw = true,
			noSystemClipboard = true,
			-- stylua: ignore
			autocmdEventsIgnore = { "TextChangedI", "TextChanged", "InsertLeave", "InsertEnter", "InsertCharPre" },
		},
	}
	config = vim.tbl_deep_extend("keep", userConfig, defaultConfig)

	-- settings to be used globally
	perf = config.performanceOpts
	useNerdfontIcons = config.useNerdfontIcons
	lessNotifications = config.lessNotifications
	defaultLogLevel = config.logLevel

	-- validate macro slots
	macroRegs = config.slots
	for _, reg in pairs(macroRegs) do
		if not (reg:find("^%l$")) then
			notify(
				('"%s" is an invalid slot. Choose only named registers (a-z).'):format(reg),
				"essential",
				vim.log.levels.ERROR
			)
			return
		end
	end

	-- clear macro slots
	if config.clear then deleteAllMacros("silent") end

	-- setup keymaps
	toggleKey = config.mapping.startStopRecording
	breakPointKey = normalizeKeycodes(config.mapping.addBreakPoint)
	local icon = config.useNerdfontIcons and " " or ""
	local dapSharedIcon = config.useNerdfontIcons and " /  " or ""

	keymap("n", toggleKey, toggleRecording, { desc = icon .. "Start/Stop Recording" })
	keymap("n", config.mapping.switchSlot, switchMacroSlot, { desc = icon .. "Switch Macro Slot" })
	keymap("n", config.mapping.editMacro, editMacro, { desc = icon .. "Edit Macro" })
	keymap("n", config.mapping.yankMacro, yankMacro, { desc = icon .. "Yank Macro" })
	-- stylua: ignore
	keymap("n", config.mapping.deleteAllMacros, deleteAllMacros, { desc = icon .. "Delete All Macros" })

	-- (experimental) if true, nvim-recorder and dap will use shared keymaps:
	-- 1) `addBreakPoint` will map to `dap.toggle_breakpoint()` outside
	-- a recording. During a recording, it will add a macro breakpoint instead.
	-- 2) `playMacro` will map to `dap.continue()` if there is at least one
	-- dap-breakpoint. If there is no dap breakpoint, will play the current
	-- macro-slot instead
	dapSharedKeymaps = config.dapSharedKeymaps or false
	local breakPointDesc = dapSharedKeymaps and dapSharedIcon .. "Breakpoint"
		or icon .. "Insert Macro Breakpoint."
	keymap("n", breakPointKey, addBreakPoint, { desc = breakPointDesc })
	local playDesc = dapSharedKeymaps and dapSharedIcon .. "Continue/Play" or icon .. "Play Macro"
	keymap("n", config.mapping.playMacro, playRecording, { desc = playDesc })
end

--------------------------------------------------------------------------------
-- STATUS LINE COMPONENTS

---returns recording status for status line plugins (e.g., used with cmdheight=0)
---@return string
function M.recordingStatus()
	if not isRecording() then return "" end
	local icon = useNerdfontIcons and "  " or ""
	return icon .. "Recording… [" .. macroRegs[slotIndex] .. "]"
end

---returns non-empty for status line plugins.
---@return string
function M.displaySlots()
	if isRecording() then return "" end
	local out = {}

	for _, reg in pairs(macroRegs) do
		local empty = getMacro(reg) == ""
		local active = macroRegs[slotIndex] == reg
		local hasBreakPoints = getMacro(reg):find(vim.pesc(breakPointKey))
		local bpIcon = hasBreakPoints and "!" or ""

		if empty and active then
			table.insert(out, "[ ]")
		elseif not empty and active then
			table.insert(out, "[" .. reg .. bpIcon .. "]")
		elseif not empty and not active then
			table.insert(out, reg .. bpIcon)
		end
	end

	local output = table.concat(out)
	if output == "[ ]" then return "" end
	local icon = useNerdfontIcons and "󰃽 " or "RECs "
	return icon .. output
end

--------------------------------------------------------------------------------

return M
Download .txt
gitextract_zqjnb51d/

├── .editorconfig
├── .emmyrc.json
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── nvim-type-check.yml
│       ├── panvimdoc.yml
│       ├── pr-title.yml
│       ├── rumdl-lint.yml
│       ├── stale-bot.yml
│       └── stylua.yml
├── .gitignore
├── .harper-dictionary.txt
├── .luarc.jsonc
├── .rumdl.toml
├── .stylua.toml
├── LICENSE
├── README.md
├── doc/
│   └── nvim-recorder.txt
└── lua/
    └── recorder.lua
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (54K chars).
[
  {
    "path": ".editorconfig",
    "chars": 393,
    "preview": "root = true\n\n[*]\nmax_line_length = 100\nend_of_line = lf\ncharset = utf-8\ninsert_final_newline = true\nindent_style = tab\ni"
  },
  {
    "path": ".emmyrc.json",
    "chars": 293,
    "preview": "{\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\"librar"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 187,
    "preview": "# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/di"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 1120,
    "preview": "name: Bug Report\ndescription: File a bug report\ntitle: \"[Bug]: \"\nlabels: [\"bug\"]\nbody:\n  - type: textarea\n    id: bug-de"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 28,
    "preview": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 768,
    "preview": "name: Feature request\ndescription: Suggest an idea\ntitle: \"Feature Request: \"\nlabels: [\"enhancement\"]\nbody:\n  - type: ch"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 174,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  "
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 619,
    "preview": "## Problem statement\n<!-- Briefly describe the issue this PR addresses. -->\n\n## Proposed solution\n<!-- Explain how this "
  },
  {
    "path": ".github/workflows/nvim-type-check.yml",
    "chars": 279,
    "preview": "name: nvim type check\n\non:\n  push:\n    branches: [main]\n    paths: [\"**.lua\"]\n  pull_request: \n    paths: [\"**.lua\"]\n\njo"
  },
  {
    "path": ".github/workflows/panvimdoc.yml",
    "chars": 936,
    "preview": "name: panvimdoc\n\non:\n  push:\n    branches: [main]\n    paths:\n      - README.md\n      - .github/workflows/panvimdoc.yml\n "
  },
  {
    "path": ".github/workflows/pr-title.yml",
    "chars": 811,
    "preview": "name: PR title\n\non:\n  pull_request_target:\n    types:\n      - opened\n      - edited\n      - synchronize\n      - reopened"
  },
  {
    "path": ".github/workflows/rumdl-lint.yml",
    "chars": 388,
    "preview": "name: Markdown linting via rumdl\n\non:\n  push:\n    branches: [main]\n    paths:\n      - \"**/*.md\"\n      - \".github/workflo"
  },
  {
    "path": ".github/workflows/stale-bot.yml",
    "chars": 799,
    "preview": "name: Stale bot\non:\n  schedule:\n    - cron: \"18 04 * * 3\" \n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n "
  },
  {
    "path": ".github/workflows/stylua.yml",
    "chars": 375,
    "preview": "name: Stylua check\n\non:\n  push:\n    branches: [main]\n    paths: [\"**.lua\"]\n  pull_request:\n    paths: [\"**.lua\"]\n\njobs:\n"
  },
  {
    "path": ".gitignore",
    "chars": 49,
    "preview": "# help-tags auto-generated by lazy.nvim\ndoc/tags\n"
  },
  {
    "path": ".harper-dictionary.txt",
    "chars": 4,
    "preview": "DAP\n"
  },
  {
    "path": ".luarc.jsonc",
    "chars": 419,
    "preview": "{\n\t\"runtime.version\": \"LuaJIT\",\n\n\t\"workspace.library\": [\"$VIMRUNTIME/lua\"], // nvim-lua runtime\n\n\t\"diagnostics\": {\n\t\t\"un"
  },
  {
    "path": ".rumdl.toml",
    "chars": 1101,
    "preview": "# DOCS https://github.com/rvben/rumdl/blob/main/docs/global-settings.md\n\n[global]\nline-length = 80\ndisable = [\"blanks-ar"
  },
  {
    "path": ".stylua.toml",
    "chars": 396,
    "preview": "# https://github.com/JohnnyMorganz/StyLua#options\n#─────────────────────────────────────────────────────────────────────"
  },
  {
    "path": "LICENSE",
    "chars": 1076,
    "preview": "MIT License\n\nCopyright (c) 2022 Christopher Grieser\n\nPermission is hereby granted, free of charge, to any person obtaini"
  },
  {
    "path": "README.md",
    "chars": 11542,
    "preview": "# Nvim-recorder 📹 <!-- rumdl-disable-line MD063 -->\n<a href=\"https://dotfyle.com/plugins/chrisgrieser/nvim-recorder\">\n<i"
  },
  {
    "path": "doc/nvim-recorder.txt",
    "chars": 12837,
    "preview": "*nvim-recorder.txt*          For Neovim          Last change: 2026 February 06\n\n========================================"
  },
  {
    "path": "lua/recorder.lua",
    "chars": 14853,
    "preview": "local M = {}\n\nlocal fn = vim.fn\nlocal v = vim.v\nlocal opt = vim.opt\nlocal keymap = vim.keymap.set\n\n-- internal vars\nloca"
  }
]

About this extraction

This page contains the full source code of the chrisgrieser/nvim-recorder GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (48.3 KB), approximately 12.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!