/salute-the-build.nvim

Compile/run different projects in neovim

Primary LanguageLuaMIT LicenseMIT

Praise The Run

Plugin for compiling/running different projects. By default python, c/c++, zig, rust, lua, sh, haskell, matlab, markdown, rmarkdown and latex are supported, but other languages can be easily added. Upon run, plugin identifies the project root from lsp. If lsp is not present/initialized, root will be determined by traversing upwards in the directory tree, searching for specified identifiers. If no identifiers are found, the directory of the current file is assumed to be the root. You can also set custom run commands per project with a specified project file, which will be automatically included in the list of root identifiers.

Default Runners

The first command, prepended to all commands regardless of what language is used, will always be cd <root>.

python

python <script> <args>

c/c++

  • With CMakeLists.txt in root directory:
mkdir build && cd build
cmake .. <args>
make -j

*Note: if clean is passed as args, rm -rf build will be executed instead.

  • With Makefile in root directory:
make -j <args>
  • If neither CMakeLists.txt nor Makefile exists, plugin will copy generic makefile that will scan all .c/.cpp files and run:
make -j <args>

zig

  • With Makefile in root directory:
make -j <args>
  • With zig.build in root directory:
zig build <args> run

rust

  • With Cargo.toml in root directory:
cargo run -- <args>

lua

  • If lua_modules directory exists:
./lua <script> <args>
  • Otherwise:
lua <script> <args>

sh

chmod +x <script>
./<script> <args>

haskell

  • If *.cabal file exists:
cabal run
  • Otherwise:
ghc -o <dirname> <script> && ./<dirname>

matlab

octave <script> <args>

markdown

pandoc --verbose -o <script:%.pdf> <args>

*Note: -o script:%.pdf will be passed only if -o is not present in args.

rmarkdown

echo "require(rmarkdown); render('<script>')" | R --vanila <args>

*Note: --vanila is passed only if args are not specifed.

latex

sh -c "pdflatex <script> && if grep -q 'bibliography' <script>; then if grep -q 'biblatex' <script>; then biber <script:%.> && pdflatex <script> && pdflatex <script>; else bibtex <script:%.> && pdflatex <script> && pdflatex <script>; fi; fi"

Installation

Add the following to your neovim configuration:

{
    'vilari-mickopf/praise-the-run.nvim',
    config = function()
        require('praise-the-run').setup()
    end
}

Configuration (optional)

Default configuration:

require('praise-the-run').setup({
    call = require('praise-the-run.project').call,
        languages = {
            python = {
                project_file = '.pyproject',
                root_identifier = {'.git', '.svn'},
                run = default_runners.python
            },
            c = {
                project_file = '.cproject',
                root_identifier = {'[Mm]akefile', 'CMakeLists.txt', '.git', '.svn'},
                run = default_runners.c
            },
            cpp = {
                project_file = '.cproject',
                root_identifier = {'[Mm]akefile', 'CMakeLists.txt', '.git', '.svn'},
                run = default_runners.cpp
            },
            zig = {
                project_file = '.zigproject',
                root_identifier = {'[Mm]akefile', 'zig.build', '.git', '.svn'},
                run = default_runners.zig
            },
            make = {
                project_file = '.cproject',
                root_identifier = {'[Mm]akefile', 'CMakeLists.txt', '.git', '.svn'},
                run = default_runners.make
            },
            cmake = {
                project_file = '.cproject',
                root_identifier = {'CMakeLists.txt', '.git', '.svn'},
                run = default_runners.cmake
            },
            rust = {
                project_file = '.rustproject',
                root_identifier = {'Cargo.toml', '.git', '.svn'},
                run = default_runners.rust
            },
            lua = {
                project_file = '.luaproject',
                root_identifier = {'lua_modules', '.git', '.svn'},
                run = default_runners.lua
            },
            sh = {
                project_file = '.shproject',
                root_identifier = {'.git', '.svn'},
                run = default_runners.sh
            },
            haskell = {
                project_file = '.hsproject',
                root_identifier = {'*.cabal', '.git', '.svn'},
                run = default_runners.haskell
            },
            matlab = {
                project_file = '.mproject',
                root_identifier = {'.git', '.svn'},
                run = default_runners.matlab
            },
            markdown = {
                project_file = '.mdproject',
                root_identifier = {'.git', '.svn'},
                run = default_runners.markdown
            },
            rmd = {
                project_file = '.rmdproject',
                root_identifier = {'.git', '.svn'},
                run = default_runners.rmarkdown
            },
            tex = {
                project_file = '.texproject',
                root_identifier = {'.git', '.svn'},
                run = default_runners.tex
            }
        }
})

Call command

Default call function will run specified command in integrated terminal:

exe 'split' | exe 'terminal %s'
call cursor(line('w$'), col('.'))

You can customize this by using a custom call command. Personally, I prefer using the terminal in a split window. Here are some additional keybindings that allow me to close the terminal when enter is pressed:

function! TerminalMappings()
    nmap <silent><buffer> <Cr> :q! \| echo('Terminal closed')<Cr>
endfunction

augroup TerminalStuff
    au!
    au TermOpen * call TerminalMappings()
augroup end

Runners

You can override any runner with a custom function:

local function custom_runner(root, args)
    -- First command of the runner will always be `cd <root>`
    local command = '<cmd> ' .. vim.api.nvim_buf_get_name(0)
    if args ~= '' then
        command = command .. ' ' .. args
    end
    return command

    -- You can do the same as above by using default runner:
    -- return require('praise-the-run.default_runners').default_runner('<cmd>', root, args)
end

require('praise-the-run').setup({
    languages = {
        <lang> = {
            run = custom_runner
        }
    }
})

The runner function takes two arguments: the root path and the provided arguments. It should always return the command to be run, represented as a string.

Add custom support for other languages

You can configure languages that are not support. <lang> should match the output of :echo &filetype for desired file type.

require('praise-the-run').setup({
    languages = {
        <lang> = {
            project_file = '.langproject',      --> optional, if nill, .<lang>project will be assigned
            root_identifier = {'.git', '.svn'}, --> optional, if nil, this will be assigned
            run = function(root, args)          --> mandatory
                return require('praise-the-run.default_runners').default_runner('cmd', root, args)
            end
        }
    }
})

Running

To compile/run language of the current file:

require('praise-the-run').run()

or with arguments:

require('praise-the-run').run('--some --args')

or with vim commands:

:ProjectRun
:ProjectRunWithArgs

Project configuration (optional)

Example project file:

{
    "pre": ["./autoconfig"], /* List of pre-run commands */
    "run": "make",           /* Command that should be run */
    "args": "-j",            /* Arguments to run command */
    "post": ["./run-bin"],   /* List of post-run commands */
}

Can be particularly useful for python projects where you need to run a specific script consistently while editing other scripts:

{
    "pre": [],
    "run": "python path/to/main.py",
    "args": "--some-arg",
    "post": [],
}

If require('praise-the-run').run(<args>) is used, the provided arguments <args> will override the arguments specified in the project file.

Opening the Project File

To open the project file in a split window, use:

require('praise-the-run').open_project_file()

or use vim command:

:OpenProjectFile

If the file doesn't exist, a dummy project file with all fields empty will be created automatically in root directory.

My keybindings

nmap <silent> <leader>p :OpenProjectFile<Cr>
nmap <silent> <leader>c :wa<Cr>:ProjectRun<Cr>
nmap <silent> <leader>C :wa<Cr>:ProjectRunWithArgs<Cr>

License: MIT