A Vim plugin for executing pathaction, the universal Makefile for the entire filesystem

The vim-pathaction Vim plugin provides an interface for executing .pathaction.yaml rules directly from Vim through the pathaction cli, a flexible tool for running commands on files and directories.

Think of pathaction like a Makefile for any file or directory in the filesystem. It uses a .pathaction.yaml file to figure out which command to run, and you can even use Jinja2 templating to make those commands dynamic. You can also use tags to define multiple actions for the exact same file type, like setting up one tag to run a script, and another to debug it.

This tool is for software developers who manage multiple projects across diverse ecosystems and want to eliminate the cognitive load of switching between different build tools, environment configurations, and deployment methods. Just run one single command on any file and trust that it gets handled correctly.

If this package helps your workflow, please show your support by ⭐ starring pathaction.el on GitHub to help more software developers discover its benefits.

Usage

Keybindings

Below are two examples of key bindings (<leader>ee to execute the “main” tag and <leader>ei to execute the “install” tag):

nnoremap <leader>ee :call pathaction#run("main")<CR>
nnoremap <leader>ei :call pathaction#run("install")<CR>
nnoremap <leader>ed :call pathaction#run("debug")<CR>

Allow the directory explicitly

By default, pathaction does not read rule-set files such as .pathaction.yaml from arbitrary directories. The target directory must be explicitly permitted.

For example, to allow Pathaction to load .pathaction.yaml rules from ~/projects and its subdirectories, run the following command:

pathaction --allow-dir ~/projects

Author and license

Copyright (C) 2021-2026 James Cherti.

Distributed under terms of the MIT license.

Installation

Installation with Vim’s built-in package manager (Vim 8 and above)

mkdir -p ~/.vim/pack/jamescherti/start
cd ~/.vim/pack/jamescherti/start
git clone --depth 1 https://github.com/jamescherti/vim-pathaction
vim -u NONE -c "helptags vim-pathaction/doc" -c q

Installation with a third-party plugin manager

You can also install this Vim plugin with any third-party plugin manager such as Pathogen or Vundle.

Links

  • vim-pathaction, a Vim plugin that allows executing the pathaction command-line tool directly from Vim.
  • The pathaction command-line tool (requirement): pathaction cli
  • For Emacs users: pathaction.el, an Emacs package that allows executing the pathaction command-line tool directly from Emacs.

pathaction.el: An Emacs package for executing pathaction rules, the universal Makefile for the entire filesystem

Build Status License

The pathaction.el Emacs package provides an interface for executing .pathaction.yaml rules directly from Emacs through the pathaction cli, a flexible tool for running commands on files and directories.

Think of pathaction like a Makefile for any file or directory in the filesystem. It uses a .pathaction.yaml file to figure out which command to run, and you can even use Jinja2 templating to make those commands dynamic. You can also use tags to define multiple actions for the exact same file type, like setting up one tag to run a script, and another to debug it.

This tool is for software developers who manage multiple projects across diverse ecosystems and want to eliminate the cognitive load of switching between different build tools, environment configurations, and deployment methods. Just run one single command on any file and trust that it gets handled correctly.

If this package helps your workflow, please show your support by ⭐ starring pathaction.el on GitHub to help more software developers discover its benefits.

Requirements

Installation

To install pathaction from MELPA:

  1. If you haven’t already done so, add MELPA repository to your Emacs configuration.

  2. Add the following code to your Emacs init file to install pathaction from MELPA:

(use-package pathaction
  :config
  (add-to-list 'display-buffer-alist '("\\*pathaction:"
                                       (display-buffer-at-bottom)
                                       (window-height . 0.33))))

Usage

Allow the directory

By default, pathaction does not read rule-set files such as .pathaction.yaml from arbitrary directories. The target directory must be explicitly permitted.

For example, to allow Pathaction to load .pathaction.yaml rules from ~/projects and its subdirectories, run the following command:

pathaction --allow-dir ~/projects

Run

To execute the pathaction action that is tagged with main, you can call the following Emacs function:

(pathaction-run "main")
  • pathaction-run: This is the main function for triggering pathaction actions.
  • "main": This is the tag used to identify a specific action. The tag you provide to the function determines which set of actions will be executed. In this case, "main" refers to the actions that are specifically tagged with this name.

Edit .pathaction.yaml

To edit a .pathaction.yaml file located in a parent directory, run the command: M-x pathaction-edit

The command prompts for selection of one of the rule-set files found in the current directory or in one of its parent directories.

Customization

Configuration Options

pathaction-term-shell (Default: explicit-shell-file-name or shell-file-name)

The shell used by the terminal emulator to execute the pathaction command (e.g., "/bin/bash" or "/bin/zsh").

pathaction-term-function (Default: #'pathaction-ansi-term)

The function used to create and execute the terminal. By default, it uses #'pathaction-ansi-term. You can customize this to use faster, third-party terminal emulators like:

  • #'pathaction-eat
  • #'pathaction-vterm

pathaction-cleanup-buffer-at-process-exit (Default: t)

If non-nil, automatically closes the terminal and kills the buffer when the process exits. Set this to nil if you want the window to remain open so you can inspect the output.

pathaction-close-window-after-execution (Default: t)

If non-nil, the pathaction window will be closed once execution is complete.

pathaction-keep-buffer-when-process-running (Default: t)

If non-nil, keeps hidden pathaction buffers alive if they have an active process.

Making pathaction open a window under the current one

To configure pathaction to open its window under the current one, you can use the display-buffer-alist variable to customize how the pathaction buffer is displayed. Specifically, you can use the display-buffer-at-bottom action, which will display the buffer in a new window at the bottom of the current frame.

Here’s the code to do this:

(add-to-list 'display-buffer-alist '("\\*pathaction:"
                                     (display-buffer-at-bottom)
                                     (window-height . 0.33)))

Hooks

  • pathaction-before-run-hook: This hook is executed by pathaction-run before the pathaction command is executed.
  • pathaction-after-create-buffer-hook: This hook is executed after the pathaction buffer is created. It runs from within the pathaction buffer, enabling further customization or actions once the buffer is available.

Saving all buffers before executing pathaction

By default, pathaction-before-run-hook only calls the pathaction-save-buffer function to save the current buffer before executing actions or commands that affect the current or any other edited buffer.

To make pathaction save all buffers, use the following configuration:

(defun my-save-some-buffers ()
  "Prevent `save-some-buffers' from prompting by passing 1 to it."
  ;; The first argument means to save all buffers without prompting
  (save-some-buffers t))

(add-hook 'pathaction-before-run-hook #'my-save-some-buffers)

Author and License

The pathaction Emacs package has been written by James Cherti and is distributed under terms of the GNU General Public License version 3, or, at your choice, any later version.

Copyright (C) 2025-2026 James Cherti

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program.

Links

  • pathaction.el, an Emacs package that allows executing the pathaction command-line tool directly from Emacs.
  • The pathaction command-line tool (requirement): pathaction cli
  • For Vim users: vim-pathaction, a Vim plugin that allows executing the pathaction command-line tool directly from Vim.

Other Emacs packages by the same author:

  • compile-angel.el: Speed up Emacs! This package guarantees that all .el files are both byte-compiled and native-compiled, which significantly speeds up Emacs.
  • outline-indent.el: An Emacs package that provides a minor mode that enables code folding and outlining based on indentation levels for various indentation-based text files, such as YAML, Python, and other indented text files.
  • easysession.el: Easysession is lightweight Emacs session manager that can persist and restore file editing buffers, indirect buffers/clones, Dired buffers, the tab-bar, and the Emacs frames (with or without the Emacs frames size, width, and height).
  • vim-tab-bar.el: Make the Emacs tab-bar Look Like Vim’s Tab Bar.
  • elispcomp: A command line tool that allows compiling Elisp code directly from the terminal or from a shell script. It facilitates the generation of optimized .elc (byte-compiled) and .eln (native-compiled) files.
  • tomorrow-night-deepblue-theme.el: The Tomorrow Night Deepblue Emacs theme is a beautiful deep blue variant of the Tomorrow Night theme, which is renowned for its elegant color palette that is pleasing to the eyes. It features a deep blue background color that creates a calming atmosphere. The theme is also a great choice for those who miss the blue themes that were trendy a few years ago.
  • Ultyas: A command-line tool designed to simplify the process of converting code snippets from UltiSnips to YASnippet format.
  • dir-config.el: Automatically find and evaluate .dir-config.el Elisp files to configure directory-specific settings.
  • flymake-bashate.el: A package that provides a Flymake backend for the bashate Bash script style checker.
  • flymake-ansible-lint.el: An Emacs package that offers a Flymake backend for ansible-lint.
  • inhibit-mouse.el: A package that disables mouse input in Emacs, offering a simpler and faster alternative to the disable-mouse package.
  • quick-sdcv.el: This package enables Emacs to function as an offline dictionary by using the sdcv command-line tool directly within Emacs.
  • enhanced-evil-paredit.el: An Emacs package that prevents parenthesis imbalance when using evil-mode with paredit. It intercepts evil-mode commands such as delete, change, and paste, blocking their execution if they would break the parenthetical structure.
  • stripspace.el: Ensure Emacs Automatically removes trailing whitespace before saving a buffer, with an option to preserve the cursor column.
  • persist-text-scale.el: Ensure that all adjustments made with text-scale-increase and text-scale-decrease are persisted and restored across sessions.
  • kirigami.el: The kirigami Emacs package offers a unified interface for opening and closing folds across a diverse set of major and minor modes in Emacs, including outline-mode, outline-minor-mode, outline-indent-minor-mode, org-mode, markdown-mode, vdiff-mode, vdiff-3way-mode, hs-minor-mode, hide-ifdef-mode, origami-mode, yafolding-mode, folding-mode, and treesit-fold-mode. With Kirigami, folding key bindings only need to be configured once. After that, the same keys work consistently across all supported major and minor modes, providing a unified and predictable folding experience.
  • buffer-guardian.el: Automatically saves Emacs buffers without requiring manual intervention. By default, it triggers a save when the user switches to another buffer, switches to another window or frame, Emacs loses focus, or the minibuffer is opened. Beyond standard file buffers, buffer-guardian also manages specialized editing buffers such as org-src and edit-indirect. Additional features, disabled by default, include periodic or idle-time saving of all buffers, automatic exclusion of remote, nonexistent, or large files, and support for custom exclusion rules via regular expressions or predicate functions.

Pathaction, a universal Makefile for your entire filesystem: Run rule-driven commands on any file or directory

License: GPL v3

The pathaction tool is a flexible command-line utility for running commands on files and directories. Just pass a file path as an argument, and it handles the rest, whether you’re working with code, media, or configurations.

Think of pathaction like a Makefile for any file or directory in the filesystem. It uses a .pathaction.yaml file to figure out which command to run, and you can even use Jinja2 templating to make those commands dynamic. You can also use tags to define multiple actions for the exact same file type, like setting up one tag to run a script, and another to debug it.

This tool is for software developers who manage multiple projects across diverse ecosystems and want to eliminate the cognitive load of switching between different build tools, environment configurations, and deployment methods. Just run one single command on any file and trust that it gets handled correctly.

If this tool helps your workflow, please show your support by ⭐ starring pathaction on GitHub to help more software developers discover its benefits.

Example

You can execute a file with the following commands:

pathaction -t main file.py

Or:

pathaction -t edit another-file.jpg

The -t option specifies the tag, allowing you to apply a tagged rule.

Here is an example of what a .pathaction.yaml rule-set file looks like:

---
actions:
  - path_match: "*.py"
    tags: main
    command:
      - "python"
      - "{{ file }}"

  - path_match: "*.jpg"
    tags:
      - edit
      - show
    command: "gimp {{ file|quote }}"

There are many ways to match paths, including using regex. See below for more details.

Requirements

Python requirements:

  • jinja2
  • schema
  • PyYAML

Editor Plugins

Installation

Here is how to install pathaction using pip:

sudo pip install --user pathaction

The pip command above will install the pathaction executable in the directory ~/.local/bin/.

Optional Dependencies

The pathaction CLI offers optional dependencies that extend its functionality. These extras can be installed according to environment requirements.

  • Colored Terminal Output (colors): Installs colorama to provide consistent cross-platform ANSI color support. This enhances the readability of standard output and error messages.

    pip install "pathaction[colors]"
  • Custom Process Title (proctitle): Installs setproctitle to rename the running process from python to pathaction. This simplifies identification in system monitoring tools such as top, htop, and ps.

    pip install "pathaction[proctitle]"

To install both extras at once, use a comma-separated list:

pip install "pathaction[colors,proctitle]"

Usage

Allow a directory

By default, pathaction does not read rule-set files such as .pathaction.yaml from arbitrary directories. The target directory must be explicitly permitted.

For example, to allow Pathaction to load .pathaction.yaml rules from ~/projects and its subdirectories, run the following command:

pathaction --allow-dir ~/projects

Rule-set files: .pathaction.yaml

The pathaction command-line tool uses regular expressions or filename pattern matching found in the rule-set file named .pathaction.yaml to associate commands with file types.

For instance, consider the following command:

pathaction -t main ~/projects/project-name/sub-project/file.py

The command above will load the .pathaction.yaml file not only from the directory where file.py is located but also from its parent directories. This loading behavior is similar to that of a .gitignore file. The rule sets from all these .pathaction.yaml files are combined. In case of conflicting rules or configurations, priority is given to the rule set that is located in the directory closest to the specified file or directory passed as a parameter to the pathaction command.

Jinja2 templating can be used to dynamically replace parts of the commands defined in the rule-set file with information about the file being executed, such as its filename and path, among other details (more on this below). In the command "python {{ file|quote }}", the placeholder {{ file|quote }} will be dynamically substituted with the path to the source code passed as a parameter to the pathaction command-line tool.

Each rule defined in the rule set file .pathaction.yaml must include at least:

  • The matching rule (e.g., a file name pattern like *.py or a regex .*py$).
  • The command or a shell command (the command and its arguments can be templated with Jinja2).

Example 1

This is what the rule-set file .pathaction.yaml contains:

---
actions:
  # *.py files
  - path_match: "*.py"
    tags: main
    command:
      - "python"
      - "{{ file }}"

  # *.sh files
  - path_match: "*.sh"
    tags:
      - main
    command: "bash {{ file|quote }}"

  - path_match: "*.sh"
    tags: install
    command: "cp {{ file|quote }} ~/.local/bin/"

Consider the following command:

pathaction source_code.py

The command above will:

  1. Load the source_code.py file.
  2. Attempt to locate .pathaction.yaml or .pathaction.yml in the directory where the source code is located or in its parent directories. The search for .pathaction.yaml follows the same approach as git uses to find .gitignore in the current and parent directories.
  3. Execute the command defined in .pathaction.yaml (e.g., pathaction will execute the command python {{ file }} on all *.py files).

Example 2

Here is another example of a rule-set file located at ~/.pathaction.yaml:

---

options:
  shell: /bin/bash
  verbose: false
  debug: false
  confirm_after_timeout: 120
  timeout: 3600
  # debug: false
  # verbose: true

vars:
  variable_name: "variable"

actions:
  # A shell is used to run the following command:
  - path_match: "*.sh"
    path_match_exclude: "*/not_this_one.sh"    # optional
    tags:
      - main
    shell: true
    command: "bash {{ file|quote }}"

  # The command is executed without a shell when shell=false
  - path_regex: '^.*ends_with_string$'
    regex_path_exclude: '^.*not_this_one$'  # optional
    tags: main
    cwd: "{{ file|dirname }}"               # optional
    shell: false                            # optional
    command:
      - "python"
      - "{{ file }}"

  # Mime types
  - mimetype: "text/x-python"
    tags: main
    shell: true
    list_commands:
      - "echo python mime type"

  - mimetype_regex: "^app.*/x-sh$"
    tags: main
    shell: true
    list_commands:
      - "echo sh file. varible_name={{ variable_name }} "

Jinja2 Variables and Filters

Jinja2 Variables

Variable Description
{{ file }} Replaced with the full path to the source code.
{{ cwd }} Refers to the current working directory.
{{ env }} Represents the operating system environment variables (dict).
{{ pathsep }} Denotes the path separator

Jinja2 Filters

  • quote: Escapes a string for use as a shell argument by wrapping it in single quotes and escaping internal single quotes. This prevents shell injection vulnerabilities when executing paths that contain spaces or punctuation. Example: "/home/user/my file.txt" | quote evaluates to '/home/user/my file.txt'

  • basename: Extracts the trailing filename or leaf component of a filesystem path. Example: "/home/user/src/main.py" | basename evaluates to "main.py".

  • dirname: Returns the parent directory portion of a filesystem path. Example: "/home/user/src/main.py" | dirname evaluates to "/home/user/src".

  • realpath: Resolves all symbolic links, relative segments (like ..), and duplicate separators to return the canonical absolute path. Example: "/usr/bin/../local/bin/python" | realpath evaluates to "/usr/local/bin/python".

  • abspath: Converts a relative path into an absolute path by prefixing it with the current working directory, without expanding symbolic links. Example: "src/main.py" | abspath evaluates to "/home/user/project/src/main.py".

  • joinpath: Combines one or more path segments using the system filesystem separator. Example: "/var/log" | joinpath("nginx", "error.log") evaluates to "/var/log/nginx/error.log".

  • joincmd: Converts an array of command-line tokens into a single properly escaped shell command string. Example: ["grep", "-i", "error log"] | joincmd evaluates to 'grep -i "error log"'.

  • splitcmd: Parses a raw shell command string into an array of distinct arguments while honoring quotation rules and escape sequences. Example: "git commit -m 'initial release'" | splitcmd evaluates to ["git", "commit", "-m", "initial release"].

  • expanduser: Replaces a leading tilde notation (~ or ~user) with the absolute path of the corresponding user home directory. Example: "~/config/tmux.conf" | expanduser evaluates to "/home/user/config/tmux.conf".

  • expandvars: Substitutes environment variables within a string matching $VARIABLE or ${VARIABLE} with their current active system values. Example: "$HOME/.config" | expandvars evaluates to "/home/user/.config".

  • shebang: Inspects a file and extracts the first line directly if it begins with an executable script prefix (#!). Example: "/home/user/script.sh" | shebang evaluates to "#!/usr/bin/env bash".

  • shebang_list: Extracts the shebang line from a file, discards the initial #! marker, and parses the remaining contents into a clean token array. Example: "/home/user/script.sh" | shebang_list evaluates to ["/usr/bin/env", "bash"].

  • shebang_quote: Extracts the shebang line from a file, strips the #! marker, and returns the runtime interpreter directive as a safely balanced, shell-quoted string. Example: "/home/user/script.sh" | shebang_quote evaluates to "/usr/bin/env bash".

  • which: Searches the system environment variable PATH to locate the absolute path of an executable binary. Raises an error if the binary cannot be found. Example: "emacs" | which evaluates to "/usr/bin/emacs".

Frequently Asked Questions

Does pathaction walk the filesystem from the current directory to the top in search of .pathaction.yaml ruleset files?

Pathaction walks from the directory containing the file passed to it and merges .pathaction.yaml rules from all allowed parent directories.

There is a security measure by default: loading rules is allowed only in directories that have been explicitly permitted using pathaction --allow-dir ~/dir/projects/, which enables access to ~/dir/projects/ and all its subdirectories. If the entire home directory is allowed with pathaction --allow-dir ~/, rules can be loaded from any directory within the home directory.

What are the differences between make and pathaction?

The make tool centers on targets and dependency tracking, making it good for compiling software based on file timestamps. In contrast, the pathaction tool acts as a universal file execution router. Passing a file path directly to Pathaction determines the correct command to run based on defined file extensions or patterns.

While make relies on project-specific files with strict syntax, Pathaction uses YAML files that cascade hierarchically across your filesystem. Much like how Git handles ignore files, Pathaction loads and merges all .pathaction.yaml rule-set files found in parent directories. This allows you to define rules in your home directory that can be overridden by specific settings within individual project folders.

For example, a Python script in ~/project_a can be routed to a local virtual environment, while a Python script in ~/project_a/project_b can trigger a Docker execution simply by defining different .pathaction.yaml files in those directories. Pathaction loads and merges all .pathaction.yaml ruleset files found in parent directories. This means that any rule in ~/project_a/project_b/.pathaction.yaml that does not match a file falls back to the rules defined in ~/project_a/.pathaction.yaml, similar to how Git handles .gitignore files.

What is the difference between Pathaction and a command such as find | xargs?

It is very different from find | xargs.

The pathaction tool functions like a customizable, developer-focused xdg-open. It acts as the intelligent router that receives each file path and automatically determines the correct command to execute based on your defined rules.

Just as xdg-open relies on rigid system MIME types to launch GUI applications, Pathaction uses your hierarchical .pathaction.yaml configurations and Jinja2 templating to dynamically run commands.

How is pathaction different from a shebang?

Shebangs are fine for basic execution, but they have limitations that Pathaction was built to address.

A shebang only defines how to execute a script. It cannot tell your system how to lint, format, debug, or test files. With pathaction, you can use tags. Passing pathaction -t run file.py executes it, while passing pathaction -t test file.py can run it through pytest.

How is pathaction different from xdg-open?

File associations such as xdg-open apply globally. Pathaction uses cascading YAML files similar to how Git handles .gitignore files. A Python script in ~/project_a can be routed to a local virtual environment, while a Python script in ~/project_a/project_b can trigger a Docker execution simply by defining different .pathaction.yaml files in those directories. Pathaction loads and merges all .pathaction.yaml ruleset files found in parent directories. This means that any rule in ~/project_a/project_b/.pathaction.yaml that does not match a file falls back to the rules defined in ~/project_a/.pathaction.yaml.

In addition to that, Pathaction uses Jinja2 templating, allowing you to dynamically build complex shell commands based on the file name, its parent directory, or environment variables.

How does the author use pathaction?

The author’s .pathaction.yaml rules function as a universal bridge across distinct software ecosystems.

  • For Python, C, C++, and related languages, rules are defined to install dependencies, build projects, execute binaries, run test suites, and launch debuggers.
  • For Ansible, rules automatically upload playbooks to remote servers, execute them, and validate their results.
  • For Emacs, rules integrate file-based actions directly with editor workflows, enabling evaluation, compilation, or linting based on context.
  • For Vim, rules provide similar editor integration, allowing files to trigger build, run, or formatting actions without manual command construction.

Pathaction operates as an IDE-like action layer for the filesystem. Instead of embedding logic inside each editor or build system, actions are described declaratively and applied uniformly across tools.

The primary advantage is cognitive simplicity. There is no need to memorize complex command-line flags or tool-specific invocation patterns. A file is passed to Pathaction with a semantic tag such as main, install, or debug, and the corresponding rule determines how the operation is executed.

License

Copyright (c) 2021-2026 James Cherti

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.

Links

Plugins for editors:

  • pathaction.el (Emacs package): Executing the pathaction command-line tool directly from Emacs.
  • vim-pathaction (Vim plugin): Executing the pathaction command-line tool directly from Vim.