Hello & a Gradle integration plugin for Kakoune

Since discovering and switching to Kakoune from NeoVim about 2 months ago, I have noticed that Kakoune’s extension model is so much simpler than Vim’s. The complexity of VimL put me off attempting to implement any sort of plugins in Vim, but since Kakoune has a simpler extension model, I decided to give it a shot.

This is one of my first attempts at creating a Kakoune plugin - a plugin that integrates the Gradle build system into Kakoune, so gradle tasks can be run without having to leave Kakoune. It’s essentially a glorified wrapper for gradle that runs gradle tasks using the :terminal command, apart from the gradle-tasks command that shows the available Gradle tasks in a Kakoune buffer, so the user can press Enter/Return to execute the desired task.

Again, this is one of my first attempts to create a Kakoune plugin, so there’s likely room for improvement. Any feedback would be much appreciated! :slightly_smiling_face:

4 Likes

Oh, I have a very complex gradle app I constantly work with in AndroidStudio – will be great to get that out into Kakoune. I will be testing this ASAP.

Hi jdugan,
I also use ‘gradle’ as my build tool and like your plugin.

‘Suggestion Box Time’:

  • a flag or bool to use a projects ./gradlew instead of the system-wide gradle command.
  • ability to verify gradle-wrapper.jar or grab one with additional positional arguments (see my script below).

verify-gradle-wrapper.zsh
#! /usr/bin/env zsh
# ----------------------------------------------------------------------------------------------------------- #
# Version control repositories that allow users to include binary files:
#     gradle/wrapper/gradle-wrapper.jar
#
# Gradle 2021, Verifying the integrity of the Gradle Wrapper JAR, The Gradle Wrapper, viewed 04 February 2021,
#              <https://docs.gradle.org/current/userguide/gradle_wrapper.html#wrapper_checksum_verification>
# ----------------------------------------------------------------------------------------------------------- #
function print_help(){
cat << EOF

Usage: ${${(%):-%x}:t} [-h|-help|--help]|[[-g|-get|--get] <version> <checksum>]|[[-c|-check|--check] <version>]

  -g, -get, --get

      VERSION="\$2" CHECKSUM="\$3"

      $ gradle wrapper \\
          --gradle-version "\$VERSION" \\
          --distribution-type all \\
          --gradle-distribution-sha256-sum "\$CHECKSUM"

      # ------------------------------------------------------------------- #
        Gradle 2021, Gradle distribution and wrapper JAR checksum reference,
          viewed 04 February 2021, <https://gradle.org/release-checksums/>
      # ------------------------------------------------------------------- #
        Integrated Development Environment (IDE) pulls:
        Complete (-all|-bin) ZIP Checksum
          gradle/wrapper/gradle.wrapper.properties
      # ------------------------------------------------------------------- #
        Command Line Interface (CLI) pulls:
        Wrapper JAR Checksum
          $ ./gradlew tasks
      # ------------------------------------------------------------------- #

  -c, -check, --check

      VERSION="\$2"

      $ cd \$PWD/gradle/wrapper

      $ curl --fail --no-progress-meter --show-error --location \\
             --output gradle-wrapper.jar.sha256 \\
             https://services.gradle.org/distributions/gradle-"\$VERSION"-wrapper.jar.sha256

      $ echo "  gradle-wrapper.jar" >> gradle-wrapper.jar.sha256
      $ sha256sum --check gradle-wrapper.jar.sha256

EOF
}
# -------------------------------------------------------------------------------------------------- #
if [ $# -eq 0 ]; then print_help; exit 1; fi
# -------------------------------------------------------------------------------------------------- #
function check_dependency() {
  if ! (builtin command -V "$1" > /dev/null 2>& 1); then
    echo "ERROR: missing dependency can't find $1" 1>& 2
    exit 1
  fi
}
# -------------------------------------------------------------------------------------------------- #
function curl_check_gradle_wrapper() {
  local version="$1"
  # ------------------------------------------------------------------------------------------------ #
  cd gradle/wrapper || { echo "cd: no such file or directory: gradle/wrapper"; exit 1; }
  # ------------------------------------------------------------------------------------------------ #
  echo "Downloading $version of gradle-wrapper.jar.sha256..."
  # ------------------------------------------------------------------------------------------------ #
  curl --fail --no-progress-meter \
       --show-error --location \
       --output gradle-wrapper.jar.sha256 \
       https://services.gradle.org/distributions/gradle-"$version"-wrapper.jar.sha256
}
# -------------------------------------------------------------------------------------------------- #
function gradle_get_wrapper() {
  local version="$1"
  local checksum="$2"
  # ------------------------------------------------------------------------------------------------ #
  echo "Wrapper JAR Checksum: $checksum"
  echo "Downloading $version of gradle-wrapper.jar..."
  # ------------------------------------------------------------------------------------------------ #
  gradle wrapper \
        --gradle-version "$version" \
        --distribution-type all \
        --gradle-distribution-sha256-sum "$checksum"
}
# -------------------------------------------------------------------------------------------------- #
function shasum_gradle_wrapper() {
  echo "checking sha256 of gradle-wrapper.jar..."
  # ------------------------------------------------------------------------------------------------ #
  echo "  gradle-wrapper.jar" >> gradle-wrapper.jar.sha256
  # ------------------------------------------------------------------------------------------------ #
  sha256sum --check gradle-wrapper.jar.sha256
  # ------------------------------------------------------------------------------------------------ #
  SHA256=$(cat gradle-wrapper.jar.sha256)
  SHA256SUM=$(sha256sum gradle-wrapper.jar)
  # ------------------------------------------------------------------------------------------------ #
  printf %s%s\\n "1) sha256:    " "$SHA256"
  printf %s%s\\n "2) sha256sum: " "$SHA256SUM"
  # ------------------------------------------------------------------------------------------------ #
  rm gradle-wrapper.jar.sha256
}
# -------------------------------------------------------------------------------------------------- #
case "$1" in
  -h | -help | --help | -\? )
    print_help >&2
    ;;
  -g | -get | --get )
    if [ "$2" ] && [ "$3" ]; then
      printf %s\\n "running: get gradle wrapper" >&2
      check_dependency gradle
      gradle_get_wrapper "$2" "$3"
    else
      printf %s\\n "Option requires two arguments: $1 <version> <checksum>" >&2
    fi
    ;;
  -c | -check | --check )
    if [ "$2" ]; then
      printf %s\\n "running: curl check gradle wrapper" >&2
      check_dependency curl
      check_dependency sha256sum
      curl_check_gradle_wrapper "$2"
      shasum_gradle_wrapper
    else
      printf %s\\n "Option requires an argument: $1 <version>" >&2
    fi
    ;;
  *)
    printf %s\\n "Option $1 invalid argument." >&2
    exit 1
    ;;
esac
# -------------------------------------------------------------------------------------------------- #
exit $?

No, I have no idea how to implement this either in kak. But I do plan to tackle it. If you get there first let me know and I’ll do like wise. Bye :wave:



TODO:
upgrade the gradle-wrapper.jar
$./gradlew wrapper --gradle-version VERSION --distribution-type TYPE --gradle-distribution-sha256-sum CHECKSUM

Thinking out loud with no research done. Could we use .editorconfig or .git to find the root directories. Maybe, maybe not duncan. Why don’t you go and do some frig’n research duncan. Your a hard man jdugan, I’ll be back with answers :sweat_smile:


A bit of research and something to dwell on.

Lenormf has ‘kakmerge’ which is a great example of a shell script interfacing with a local binary and kakoune. Likewise a Gradle integration with the shell could be implemented via a ‘gradle task’ from a ‘build.gradle’ file similar to Lenormf and your gradle_wrap.sh does with kakoune. See example below of a Gradle task with shell script.

This could potentially work for a verify-gradle-wrapper.sh task.

login.sh
#!/bin/bash

echo Enter username:
read username
echo Enter password:
if [ -t 0 ] ; then # if connected to a terminal, do not echo the password input
    stty -echo
    read password
    stty echo
    echo
else
    read password
fi

if [ "$username" = "secret-user" ] && [ "$password" = "secret-password" ] ; then
    echo "Welcome, $username!"
else
    echo "Bad credentials!"
    exit 1
fi
build.gradle
def login = tasks.register('login', Exec) {
    def loginProvider = providers.credentials(PasswordCredentials, 'login')
    inputs.property('credentials', loginProvider)

    commandLine = ['sh', 'login.sh']
    doFirst {
        def loginCredentials = loginProvider.get()
        standardInput = new ByteArrayInputStream("$loginCredentials.username\n$loginCredentials.password".getBytes())
    }
}

tasks.register('doAuthenticated') {
    dependsOn(login)
    doLast {
        println 'Doing authenticated task'
    }
}

Finally, the gradle-completion repository states: '[The] ./gradlew may not work on Linux if you don’t have . on your $PATH, so I recommend adding it in your [kakoune script] file: export PATH=".:$PATH"'. Setting a simple environment variable inside kak-gradle could be the answer.

Lenormf 2021, ‘kakmerge: merge tool for git’, Github, viewed 26 February 2021, GitHub - lenormf/kakmerge: A Kakoune-based mergetool for Git

Gradle 2021, ‘Supply credentials to external tool Sample’, Gradle v6.8.3, viewed 26 February 2021, Supply credentials to external tool Sample

Gradle 2021, ‘Troubleshooting’, Gradle Completion, viewed 26 February 2021, GitHub - gradle/gradle-completion: Gradle tab completion for bash and zsh

NOTE Gradle also does: C++, Swift, etc…

Gradle 2021, Sample Index, Gradle v6.8.3, viewed 26 February 2021, Sample Index


I’m going to see if I can get the task working with the verify script in the next week or so. Cya.


Perfect timing jdugan, I’m getting back into the projects and hitting the gradle manual for custom task creation. Definitely will give you some feedback based on my usage.

Cool buddy, bye :wave:

build.gradle.kts - jlink task
  /* ilya-g 2020, build.gradle.kts, kotlin-jlink-examples, gradle, viewed 01 March 2021,
   *              https://github.com/ilya-g/kotlin-jlink-examples/blob/master/gradle/app/build.gradle.kts
   */
  register<Exec>("jlink") {
    description = "Build kowsky.kak module jar with an optimised custom runtime image"
    val outputDir by extra("$buildDir/jrt-kowsky-kak")
    inputs.files(configurations.runtimeClasspath)
    inputs.files(jar)
    outputs.dir(outputDir)
    doFirst {
      val modulePath = files(jar) + configurations.runtimeClasspath.get()
      logger.lifecycle(modulePath.joinToString("\n", "jlink module path:\n"))
      delete(outputDir)
      commandLine("$javaHome/bin/jlink",
                  "--module-path",
                  listOf("$javaHome/jmods/", modulePath.asPath).joinToString(File.pathSeparator),
                  "--add-modules", moduleName,
                  "--output", outputDir
      )
    }
  }
1 Like

Those both sound like great suggestions. I think a boolean option called “gradle-use-gradlew” would satisfy the first, although the “root” of the project will likely need to be found (AKA a directory with build.gradle in it), so the ./gradlew executable can be found.

As for the second - I’m going to need to play around with your script to see exactly how it works.

Edit: just noticed your edit. I wouldn’t rely on using the .git directory to find the project root, since a different version control system may be used, such as Mercurial. Good idea though - keep 'em coming… :slightly_smiling_face:

I finally was able to find time to sit down and implement some of these ideas.

kak-gradle now has a gradle_use_gradlew option, which if set to true will cause kak-gradle to attempt to use the project’s wrapper to perform gradle operations instead of the systemwide installation. This doesn’t seem to support gradle subprojects yet, since the way the ‘find the root directory’ algorithm works is rather crude.

Feel free to try it out and see if there’s anything else that you’d like to suggest.

Hey jdugan,
been working on kak-gradle.kak just a refactor and some idea’s thrown in, it’s not finished or finalised.

Take a look, tell me what you like, what you don’t like, and any ideas you might have.

It’s an public ‘snippet’ repository under my account. If any problems just let me know. Talk later, bye :wave:

SNIPPET:

kak-gradle-WIP.sh

Change file extension: *.sh -> *.kak

git clone https://KJ_Duncan@bitbucket.org/snippets/KJ_Duncan/kxpAbn/jdugan6240-kak-gradle-wipkak.git

kak-gradle-WIP.(sh|kak)
# References:
# -----------
# Alexherbo2 2020, plug.kak, require-module, ModuleLoaded, viewed 04 March 2021, https://github.com/alexherbo2/plug.kak/blob/master/rc/plug.kak
# Gradle 2020, Always define a settings file, Organizing Gradle Projects, Authoring Gradle Builds, Gradle User Manual, v6.8.3, p.378, viewed 04 March 2021,
# Gradle-Completion 2020, _gradle-set-project-root-dir, _gradle, viewed 04 March 2021, https://github.com/gradle/gradle-completion/blob/master/_gradle#L3
# Kakoune 2020, git.kak, mawww/kakaoune/rc/tools, viewed 04 March 2021, https://github.com/mawww/kakoune/blob/master/rc/tools/git.kak
#
# SOURCE FILE:
# ------------
# JDugan6240 2021, kak-gradle, rc/kak-gradle.kak, viewed 04 March 2021, https://github.com/jdugan6240/kak-gradle
#
# SNIPPET:
# --------
# kak-gradle-WIP.sh
# git clone https://KJ_Duncan@bitbucket.org/snippets/KJ_Duncan/kxpAbn/jdugan6240-kak-gradle-wipkak.git
#
provide-module gradle-build-tool %{

  declare-option -hidden str gradle_system %sh{ printf "%s/../src/%s" "${kak_source%/*}" "gradle_system.sh" }
  declare-option -hidden str gradlew_wrapper %sh{ printf "%s/../src/%s" "${kak_source%/*}" "gradlew_wrapper.sh" }

  declare-option -hidden str gradle_build_root_dir "nay"

  declare-option global str gradle_command ""
  declare-option global str gradle_wrapper_command ""
  # ------------------------------------------------------------------------------------------------- #
  declare-option -docstring %{
    Determines whether to use the project's gradle
    wrapper or the system wide gradle installation
  } bool use_gradlew false
  # ------------------------------------------------------------------------------------------------- #
  # Enable the highlighters for the gradle-tasks filetype
  hook -group gradle-tasks-syntax global WinSetOption filetype=gradle-tasks %{
    add-highlighter buffer/gradle_tasks_buffer ref gradle_tasks_buffer
    hook -always -once window WinSetOption filetype=.* %{
      remove-highlighter buffer/gradle_tasks_buffer
    }
  }
  # ------------------------------------------------------------------------------------------------- #
  # Enable the highlighters for the gradle-deps filetype
  hook -group gradle-deps-syntax global WinSetOption filetype=gradle-deps %{
    add-highlighter buffer/gradle_dep_buffer ref gradle_dep_buffer
    hook -always -once window WinSetOption filetype=.* %{
      remove-highlighter buffer/gradle_dep_buffer
    }
  }
  # ------------------------------------------------------------------------------------------------- #
  # Declare highlighters for the tasks buffer
  add-highlighter global/gradle_tasks_buffer group
  add-highlighter global/gradle_tasks_buffer/task regex "(^[a-zA-Z0-9]*)( - [^\n]*)" 0:string 1:type

  # Declare highlighters for the dependencies buffer
  add-highlighter global/gradle_dep_buffer group
  add-highlighter global/gradle_dep_buffer/category   regex "(^[a-zA-Z0-9]*)( - [^\n]*)" 0:string 1:type
  add-highlighter global/gradle_dep_buffer/dependency regex "([+\\]---)( [^\n]*)" 0:keyword 1:string
  add-highlighter global/gradle_dep_buffer/symbol     regex "(\|)" 0:string
  add-highlighter global/gradle_dep_buffer/no_deps    regex "^No dependencies$" 0:keyword
  add-highlighter global/gradle_dep_buffer/legend     regex "(\([*n]\))( [^\n]*)" 0:string 1:type
  # ------------------------------------------------------------------------------------------------ #
  # ------------------------------------------------------------------------------------------------ #
  # ENTRY POINT FOR KAK-GRADLE?
  # A USE-CASE FOR THE USER KNOWS BEST:
  #    define-command -menu %{ }
  #       case "$1" in
  #        reckless) recursively-set-project-root-dir ;;
  #        cautious) set-project-root-dir ;;
  #        *) "occupational health and safety" ;;
  #       esac
  #
  # If we found a (build|settings).gradle file, then we found the gradle root directory
  define-command -hidden gradle-build-root-dir %{ evaluate-commands %sh{

    # NOTE: uncontrolled recursion from leaf or node to users top level directory,
    #       kakoune does not enforce a directory ceiling/floor,
    #       allot of information on that path and no guarantees.
    #
    # .
    # ├── gradle.properties
    # └── settings.gradle
    # ├── subproject-a
    # │ └── build.gradle
    # └── subproject-b
    #   └──build.gradle
    #
    # ------------------------------------------------------------------------------------------------ #
    function recursively-set-project-root-dir() {
      local dir=`pwd`
      project_root_dir=`pwd`
      while [[ $dir != '/' ]]; do
        if [[ -f "$dir/settings.gradle" || -f "$dir/settings.gradle.kts" || -f "$dir/gradlew" ]]; then
          project_root_dir=$dir
          return 0
        fi
        dir="$(dirname "$dir")"
      done
      return 1
    }
    # ------------------------------------------------------------------------------------------------ #
    function set-project-root-dir() {
      out=$(find -P -mount -maxdepth 1 -type f -regex '^.*/build.gradle[.kts]*?' | sed 's/^.\///')

      if [[ "$out" == *"build.gradle"* ]]; then
        cur_dir=$(realpath $(dirname "$kak_source"))
        printf "set-option global gradle_build_root_dir %s\n" "$cur_dir"
      fi
    }

  }}
  # ------------------------------------------------------------------------------------------------- #
  # Determine if we need to use the gradle wrapper, or use the systemwide gradle command
  define-command -hidden -docstring "variable initialisation on ModuleLoad" \
    gradle-module-initialisation %{ try %{
      gradle-build-root-dir
      evaluate-commands %opt{gradle-build-root-dir}
      try %{
        evaluate-commands %opt{use_gradlew}
        set-option global gradle_wrapper_command %opt{gradle_build_root_dir}
        set-option -add global gradle_wrapper_command "/gradlew"
        set-option global gradle_command "gradlew"
      } catch %{
        set-option global gradle_command "gradle"
      }
    } catch %{
      fail "No build.gradle(.kts) file found in working directory"
    }
  }
  # ------------------------------------------------------------------------------------------------- #
  define-command -docstring "Execute arbitrary gradle command" \
    -params .. gradle %{ evaluate-commands %sh{
      # Determine if we need to use the gradle wrapper, or use the systemwide gradle command
      if [[ "$kak_opt_use_gradlew" = "true" ]]; then
        echo "terminal ${kak_opt_gradlew_wrapper} $kak_opt_gradle_build_root_dir $@"
      else
        echo "terminal ${kak_opt_gradle_system} $@"
      fi
    }
  }
  # ------------------------------------------------------------------------------------------------- #
  # +-------------------------------------------+
  # |            DISTRIBUTION URL               |
  # +----------+---------+---------+------------+
  # | Constant | Version |  Type   | Extension  |
  # +----------+---------+---------+------------+
  # | gradle      6.8.3    wrapper   jar.sha256 |
  # | gradle      6.8.3    docs      zip        |
  # | gradle      6.8.3    docs      zip.sha256 |
  # | gradle      6.8.3    src       zip        |
  # | gradle      6.8.3    src       zip.sha256 |
  # | gradle      6.8.3    bin       zip        |
  # | gradle      6.8.3    bin       zip.sha256 |
  # | gradle      6.8.3    all       zip        |
  # | gradle      6.8.3    all       zip.sha256 |
  # +-------------------------------------------+----------------------------------------------------- #
  define-command --params 1.. gradle-commands %{ evaluate-commands %sh{
    # ------------------------------------------------------------------------------------------------ #
    # HELPER FUNCTION -------------------------------------------------------------------------------- #
    function make_named_pipe() {
      # directory creation MODE: 100700
      tmp=$(mktemp -d "${TMPDIR:-/tmp}/kak-gradle.XXXXXXXX")
      fifo=$tmp/fifo
      # MODE: rw-r-00640, rw-r-r-00644, rw-rw-00660, rw-rw-r-00664
      mkfifo --mode 00640 ${fifo}

      # TEST: does this work?
      printf %s\n "evaluate-commands -client "$kak_client" %{
                    hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -rf ${tmp} } }
                  }"

      return ${fifo}
    }
    # ------------------------------------------------------------------------------------------------ #
    # CONSUMER FUNCTIONS ----------------------------------------------------------------------------- #
    function consume_gradle_init() {
    }
    # ------------------------------------------------------------------------------------------------ #
    function consume_gradle_wrapper() {
      # TODO: make it work with positional arguments
      version="$1"
      type=""
      case "$2" in
        "all")     type="all" ;;
        "bin")     type="bin" ;;
        "docs")    type="docs" ;;
        "src")     type="src" ;;
        "wrapper") type="wrapper" ;;
        *) ;;
      esac

      distributionUrlZip="https://services.gradle.org/distributions/gradle-${version}-${type}.zip"
      distributionUrlZipSha256="https://services.gradle.org/distributions/gradle-${version}-${type}.zip.sha256"
      distributionUrlJarSha256="https://services.gradle.org/distributions/gradle-${version}-${type}.jar.sha256"
    }
    # ------------------------------------------------------------------------------------------------ #
    # PRODUCER FUNCTIONS ----------------------------------------------------------------------------- #
    function show_gradle_task_custom() {
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_test() {
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_projects() {
      # TEST: info -title
      projects=$($kak_opt_gradle_command projects | grep -E "Root project[^\n]+|Project")
      printf %s\n%s\n "info -title \"Subprojects\"" "${projects}"
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_task_help() {
      pipe=$(make_named_pipe)
      # ---------------------------------------------------------------------------------------------- #
      # Run "gradle help --task <taskname>" in the background and extract strictly the task help
      # FIXME: grep regex
      ( $kak_opt_gradle_command help --task "$@" | grep '^[a-zA-Z0-9]* -' > ${pipe} 2>&1 & ) > /dev/null 2>&1 < /dev/null
      # ---------------------------------------------------------------------------------------------- #
      printf %s "evaluate-commands -try-client '$kak_opt_docsclient' %{
                  edit! -fifo ${pipe} *gradle*
                  set-option buffer filetype gradle-task-help
                }"
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_tasks() {
      pipe=$(make_named_pipe)
      # ---------------------------------------------------------------------------------------------- #
      # Run "gradle tasks" in the background and extract strictly the task names
      ( $kak_opt_gradle_command tasks | grep '^[a-zA-Z0-9]* -' > ${pipe} 2>&1 & ) > /dev/null 2>&1 < /dev/null
      # ---------------------------------------------------------------------------------------------- #
      printf %s "evaluate-commands -try-client '$kak_opt_docsclient' %{
                  edit! -fifo ${pipe} *gradle*
                  set-option buffer filetype gradle-tasks
                  map buffer normal '<ret>' ':<space>gradle-fifo-operate<ret>'
                }"
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_dependencies() {
      pipe=$(make_named_pipe)
      # ---------------------------------------------------------------------------------------------- #
      # Run "gradle dependencies" in the background and extract the dependencies and "legend"
      ( $kak_opt_gradle_command dependencies | grep -E '^[a-zA-Z0-9]* -|[+\]--- |No dependencies|\([*n]\)|^$' > ${pipe} 2>&1 & ) > /dev/null 2>&1 < /dev/null
      # ---------------------------------------------------------------------------------------------- #
      printf %s "evaluate-commands -try-client '$kak_opt_docsclient' %{
                  edit! -fifo ${pipe} *gradle*
                  set-option buffer filetype gradle-deps
                }"
    }
    # ------------------------------------------------------------------------------------------------ #
    # GRADLE CONSTANTS DECISION TREE ----------------------------------------------------------------- #
    case "$1" in
      init)         consume_gradle_init "$@" ;;
      wrapper)      consume_gradle_wrapper "$@" ;;
      dependencies) show_gradle_dependencies "$@" ;;
      custom)       show_gradle_task_custom "$@" ;;
      help)         show_gradle_task_help "$@" ;;
      tasks)        show_gradle_tasks "$@" ;;
      test)         show_gradle_test "$@" ;;
    esac
  }}
  # ------------------------------------------------------------------------------------------------- #
  define-command -hidden gradle-fifo-operate %{ evaluate-commands -save-regs t %{
    execute-keys -save-regs '' "ghw"
    set-register t %val{selection}
    evaluate-commands %sh{
      task="${kak_reg_t%:*}"
      echo "terminal ${kak_opt_gradle_system} $task"
    }
  }}
}
# ------------------------------------------------------------------------------------------------- #

All fair comments,
I should have clearly stated to view the file as ‘template only’ it is a none functioning script implementation. The acronym WIP - Work In Progress, was an assumption on my part.

Back to the discussion, we are looking at the file’s (kak-gradle.kak) code base from differing points off view. Which is great!

Let me explain, as Gradle provides like git does, refer to git.kak, a bunch of utility constants. These constants provide a single point of entry/maintenance. I look at kak-gradle.kak and see duplication of logic that can be abstracted into a single point, the case statement (decision tree).

As with the previous paragraph the make_named_pipe function also provides an easily maintainable call site as there is no code duplication.

The %sh{} is called multiple times in kak-gradle.kak, for me this means it needs to be only called once, hence the decision tree rests inside this single %sh{} call. Again for me this provides a flaunt code base for maintenance, extension, and contraction. A requirement in response to kakoune’s awesome pace of continuous development.

Next, kak’s Module system provides lazy loading so when the user sparks up a kak session the file (kak-gradle.kak) is not loaded each and every time, only on upon the users request.

define-command -menu is something that I have never explored. Also I’ve just run out off things to say.

Till next time, thanks for you feedback and I will work out what the hell I was thinking with the below method call.

It had a purpose, perhaps this was it. FIXME.

Bye :wave:

I don’t have a lot of time to look at this right now, so here are my initial thoughts:

  • The function construct doesn’t appear to be POSIX-compliant. This is a minor nitpick, but one that may prevent the plugin from working for some.
  • I really like the direction you chose to implement the “find the root directory” functionality. That’s a really elegant way to go about it.
  • I’m really not a fan of the gradle-commands command as you implemented it.Using a case statement to select between all of these shell functions seems a bit over-engineered, and this could just as easily have been implemented as Kakoune commands, negating the need for the case statement entirely.

Those are my initial thoughts - I’ll have more when I come back from work.

OK, now that I’ve had a better chance to look at it, here’s my full review:

provide-module gradle-build-tool %{

To be honest, I’m not sure this plugin makes sense as a module. The only thing this enables is running a hook on module load, which I don’t think this plugin needs. I’m certainly not opposed to the idea, but I don’t see any real benefit to wrapping this inside a module and requiring a require-module call.

define-command -hidden gradle-build-root-dir %{ evaluate-commands %sh{

    # NOTE: uncontrolled recursion from leaf or node to users top level directory,
    #       kakoune does not enforce a directory ceiling/floor,
    #       allot of information on that path and no guarantees.
    #
    # .
    # ├── gradle.properties
    # └── settings.gradle
    # ├── subproject-a
    # │ └── build.gradle
    # └── subproject-b
    #   └──build.gradle
    #
    # ------------------------------------------------------------------------------------------------ #
    function recursively-set-project-root-dir() {
      local dir=`pwd`
      project_root_dir=`pwd`
      while [[ $dir != '/' ]]; do
        if [[ -f "$dir/settings.gradle" || -f "$dir/settings.gradle.kts" || -f "$dir/gradlew" ]]; then
          project_root_dir=$dir
          return 0
        fi
        dir="$(dirname "$dir")"
      done
      return 1
    }
    # ------------------------------------------------------------------------------------------------ #
    function set-project-root-dir() {
      out=$(find -P -mount -maxdepth 1 -type f -regex '^.*/build.gradle[.kts]*?' | sed 's/^.\///')

      if [[ "$out" == *"build.gradle"* ]]; then
        cur_dir=$(realpath $(dirname "$kak_source"))
        printf "set-option global gradle_build_root_dir %s\n" "$cur_dir"
      fi
    }

  }}

Nice job on this - this seems to be a great way to approach the “find the root directory” problem. This currently doesn’t do anything when the gradle-build-root-dir command is called, though, since none of the shell functions you define are actually called anywhere. Also, the function and return keywords appear to be zsh-only as far as I can tell, so the function declarations and return command would need to be changed to adhere to POSIX standards (not everyone uses zsh).

define-command -hidden -docstring "variable initialisation on ModuleLoad" \
   gradle-module-initialisation %{ try %{
     gradle-build-root-dir
     evaluate-commands %opt{gradle-build-root-dir}
     try %{
       evaluate-commands %opt{use_gradlew}
       set-option global gradle_wrapper_command %opt{gradle_build_root_dir}
       set-option -add global gradle_wrapper_command "/gradlew"
       set-option global gradle_command "gradlew"
     } catch %{
       set-option global gradle_command "gradle"
     }
   } catch %{
     fail "No build.gradle(.kts) file found in working directory"
   }
 }

Assuming this is meant to be called when the module gets loaded, this will call gradle-build-root-dir whenever the module is loaded, regardless of if the user plans on using the gradle wrapper or the systemwide install. The root directory isn’t needed when the systemwide install is used, so computing it is useless in that case.

define-command --params 1.. gradle-commands %{ evaluate-commands %sh{
    # ------------------------------------------------------------------------------------------------ #
    # HELPER FUNCTION -------------------------------------------------------------------------------- #
    function make_named_pipe() {
      # directory creation MODE: 100700
      tmp=$(mktemp -d "${TMPDIR:-/tmp}/kak-gradle.XXXXXXXX")
      fifo=$tmp/fifo
      # MODE: rw-r-00640, rw-r-r-00644, rw-rw-00660, rw-rw-r-00664
      mkfifo --mode 00640 ${fifo}

      # TEST: does this work?
      printf %s\n "evaluate-commands -client "$kak_client" %{
                    hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -rf ${tmp} } }
                  }"

      return ${fifo}
    }
    # ------------------------------------------------------------------------------------------------ #
    # CONSUMER FUNCTIONS ----------------------------------------------------------------------------- #
    function consume_gradle_init() {
    }
    # ------------------------------------------------------------------------------------------------ #
    function consume_gradle_wrapper() {
      # TODO: make it work with positional arguments
      version="$1"
      type=""
      case "$2" in
        "all")     type="all" ;;
        "bin")     type="bin" ;;
        "docs")    type="docs" ;;
        "src")     type="src" ;;
        "wrapper") type="wrapper" ;;
        *) ;;
      esac

      distributionUrlZip="https://services.gradle.org/distributions/gradle-${version}-${type}.zip"
      distributionUrlZipSha256="https://services.gradle.org/distributions/gradle-${version}-${type}.zip.sha256"
      distributionUrlJarSha256="https://services.gradle.org/distributions/gradle-${version}-${type}.jar.sha256"
    }
    # ------------------------------------------------------------------------------------------------ #
    # PRODUCER FUNCTIONS ----------------------------------------------------------------------------- #
    function show_gradle_task_custom() {
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_test() {
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_projects() {
      # TEST: info -title
      projects=$($kak_opt_gradle_command projects | grep -E "Root project[^\n]+|Project")
      printf %s\n%s\n "info -title \"Subprojects\"" "${projects}"
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_task_help() {
      pipe=$(make_named_pipe)
      # ---------------------------------------------------------------------------------------------- #
      # Run "gradle help --task <taskname>" in the background and extract strictly the task help
      # FIXME: grep regex
      ( $kak_opt_gradle_command help --task "$@" | grep '^[a-zA-Z0-9]* -' > ${pipe} 2>&1 & ) > /dev/null 2>&1 < /dev/null
      # ---------------------------------------------------------------------------------------------- #
      printf %s "evaluate-commands -try-client '$kak_opt_docsclient' %{
                  edit! -fifo ${pipe} *gradle*
                  set-option buffer filetype gradle-task-help
                }"
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_tasks() {
      pipe=$(make_named_pipe)
      # ---------------------------------------------------------------------------------------------- #
      # Run "gradle tasks" in the background and extract strictly the task names
      ( $kak_opt_gradle_command tasks | grep '^[a-zA-Z0-9]* -' > ${pipe} 2>&1 & ) > /dev/null 2>&1 < /dev/null
      # ---------------------------------------------------------------------------------------------- #
      printf %s "evaluate-commands -try-client '$kak_opt_docsclient' %{
                  edit! -fifo ${pipe} *gradle*
                  set-option buffer filetype gradle-tasks
                  map buffer normal '<ret>' ':<space>gradle-fifo-operate<ret>'
                }"
    }
    # ------------------------------------------------------------------------------------------------ #
    function show_gradle_dependencies() {
      pipe=$(make_named_pipe)
      # ---------------------------------------------------------------------------------------------- #
      # Run "gradle dependencies" in the background and extract the dependencies and "legend"
      ( $kak_opt_gradle_command dependencies | grep -E '^[a-zA-Z0-9]* -|[+\]--- |No dependencies|\([*n]\)|^$' > ${pipe} 2>&1 & ) > /dev/null 2>&1 < /dev/null
      # ---------------------------------------------------------------------------------------------- #
      printf %s "evaluate-commands -try-client '$kak_opt_docsclient' %{
                  edit! -fifo ${pipe} *gradle*
                  set-option buffer filetype gradle-deps
                }"
    }
    # ------------------------------------------------------------------------------------------------ #
    # GRADLE CONSTANTS DECISION TREE ----------------------------------------------------------------- #
    case "$1" in
      init)         consume_gradle_init "$@" ;;
      wrapper)      consume_gradle_wrapper "$@" ;;
      dependencies) show_gradle_dependencies "$@" ;;
      custom)       show_gradle_task_custom "$@" ;;
      help)         show_gradle_task_help "$@" ;;
      tasks)        show_gradle_tasks "$@" ;;
      test)         show_gradle_test "$@" ;;
    esac
  }}

Honestly, I don’t understand why you structured this command like this. Combining all of these commands into a single entity adds considerable complexity - complexity that I don’t believe is warranted. The case statement could be completely eliminated by simply extracting each of these shell functions into a separate Kakoune command like things were before. If there’s a benefit to this structure that I’m not seeing, please let me know.

You could very well take all of this feedback with a grain of salt - this plugin is feature-complete for what I need it to do, so I don’t have a lot of motivation to continue developing it further. If you would like to take over, let me know and I’ll point my README to your repo.