I’ve been trying to integrate Kakoune with nnn the last little bit, to try to achieve something similar to vim + vim-vinegar + netrw. In other words, I would like to hit - to enter nnn in the parent directory for the active buffer, select a file using nnn, and have it put me back into kakoune with the current buffer set to the selected file from nnn, and close nnn. Preferably I would like this to all happen in the same window.
I believe this is possible using nnn -p -, which from here I gather will use nnn as a selector and output the results to stdout. I’ve seen other people on this forum asking how to achieve something similar, and have been directed to look into connect.kak. I’ve looked into this, and have figured out how to open it in another window (currently kitty, I’m also in the middle of configuring :terminal to open a new i3 window), but it does not close after having selected a file and seems to be leveraging a workflow around VISUAL=edit and setting up the edit command to open a file in the current kakoune session. I understand how useful/powerful that can be in a lot of contexts, but it seems like it shouldn’t be necessary for this because nnn already provides a way to output a file to stdout, and I would like to run this all in the current window.
I think my unix-fu is just lacking (long time linux user - long time bash avoider), I’d really appreciate if someone more skilled in this area could provide some guidance.
It shows how to „suspend and resume“ kakoune and has an example with ranger. Ranger opens in the same window as a selector and closes again, going back to kakoune where a buffer opens with the selected file.
I knew I’d seen this before somewhere, thanks! I think I saw that wiki page shortly after I started using Kakoune, but had mostly forgotten about it and it wasn’t showing up in any of my searches. Really appreciate it, I’ll give that a shot tonight!
@alexherbo2 that is awesome, I use a bit of a hack (open tmux pane, then send-keys to maximize it) – works well enough but having a baked in solution would be awesome.
Not that I’m criticizing the code there, but is that the simplest means of using CLI apps? I’m curious.
In this case, I wish It were simple to have kakoune create a split of nnn, and then nnn could just pass the select to the currently running kakoune session instead of opening a new kakoune session as a split in the terminal.
Unless of course kak -c can take a PID, and you can chain that with -e.
So kak -c kak_client_pid -e 'edit $nnn_selection' or something like that.
Hi parasrah,
I also use and enjoy nnn for its directory pinning. Something else to consider is using it with tmux as this allows the use of both the kak (left pane) and nnn (right pane). While in nnn (right pane) I issue the e command which opens the file in kak (left pane). Issuing the tmux commands C-b z and C-b l to switch between kak (left pane) and nnn (right pane) provides a nice alternative to continuous open and close of nnn.
where C-b = send prefix
z = resize pane -Z (zoom)
l = select pane -R (right)
# .bashrc
export EDITOR="kak"
export NNN_USE_EDITOR="$EDITOR"
# https://github.com/robertmeta/kakfiles/blob/master/kakrc#L219
define-command nnn -params .. -file-completion %(connect-horizontal; connect-terminal nnn %arg(@)) -docstring "Open with nnn"
# or now with connect.kak
:t nnn
A nice alternative is broot which I use for single pane viewing without switching tmux panes.
# Yes Alex and Robert have got you covered on broot as well
define-command broot -params .. -file-completion %(connect-terminal broot %arg(@)) -docstring "Open with broot"
Thanks everyone for your suggestions! I actually already had a pretty good workflow of keeping an nnn pane up side b side using kak-connect and i3 (I don’t use tmux) that is very similar to that @duncan. It occurred to me that I actually just wanted the appearance of nnn taking up my current window, which is complicated to achieve for real. For options to achieve that for real, I’d recommend taking a look at connect.kak, @alexherbo2 recently added :connect-detach that works really well for those that launch kakoune using kak-connect. They also suggested window swallowing, which after taking a look at looks like exactly what I want. It gives the appearance of consuming the current window, without all the complications of actually doing that. Unfortunately I haven’t really had time to dig into that too much, so in the short term I’ve fallen back to opening up a new kitty tab and leveraging kak-connect using the following command:
I will probably improve on this solution to avoid the shell expansion, and maybe try to stop it flickering when it returns to the kakoune instance (because it initially shows the buffer it was previously in). Anyone that comes to this thread looking for a similar solution is more than welcome to take a look at my dotfiles to see my solution and how it evolves.
Mind you right now all my kitty commands rely on a single instance of kitty running using kitty --single-instance so it always opens in the currently scoped kitty window/instance as opposed to the one where kak was launched. I am going to try to work around that using the listen_on config for kitty so I can just launch kitty with no arguments again
How do you open kak from your project root ? I think I got something as kak $(nnn -p -) as entrypoint but it feels inconsistent pressing <Cr> instead of e. I’d like to not poorly use nnn as the edited files will be relative to their last parent directory which sucks.
Or maybe I should just kak then e
PS: Ok then just kak . and explore-files will just do the works. Better !
This C++17 version includes several improvements over the previous version:
Uses std::filesystem for file existence checking (C++17 feature)
Separates functionality into distinct functions for better readability and maintainability
Includes more detailed error handling and reporting
Uses string manipulation helpers for escaping spaces
Provides comprehensive documentation comments
To compile this program, you’ll need a C++17 compliant compiler with filesystem support:
g++ -std=c++17 -lstdc++fs nnnkak.cpp -o nnnkak
Note that the -lstdc++fs flag is needed for linking against the standard C++ filesystem
library.
This program maintains the same functionality as the original Raku script but with a more
structured and maintainable approach. It’s also more robust in terms of error handling and
reporting.
NEED TO CHANGE TO YOUR PATH: const std::string NNN_SEL = "/home/USER/.config/nnn/.selection";
nnnkak.cpp
/**
* @file nnnkak.cpp
* @brief A utility program that interfaces between NNN and Kakoune editors.
*
* This program reads file paths from NNN selection, escapes spaces if necessary,
* and sends them to Kakoune for editing.
*/
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <memory>
#include <array>
#include <filesystem>
/**
* @brief Path to NNN selection file
*/
const std::string NNN_SEL = "/home/USER/.config/nnn/.selection";
/**
* @brief Kakoune client identifier
*/
const std::string KAK_CLIENT = "client0";
/**
* @brief Command prefix for Kakoune evaluation
*/
const std::string KAK_EVAL = "evaluate-commands -try-client " + KAK_CLIENT + " edit -existing ";
/**
* @brief Kakoune session name
*/
const std::string KAK_SESSION = "nnn-session";
/**
* @brief Escape spaces in a file path by replacing them with '\\ '
*
* @param path The original file path
* @return std::string The escaped file path
*/
std::string escapeSpaces(const std::string& path) {
std::string result = path;
size_t pos = 0;
while ((pos = result.find(' ', pos)) != std::string::npos) {
result.replace(pos, 1, "\\ ");
pos += 2; // Move past the replaced space
}
return result;
}
/**
* @brief Execute a command and capture its output
*
* @param cmd The command to execute
* @return std::string The captured output
*/
std::string executeCommand(const std::string& cmd) {
std::array<char, 1024> buffer;
std::string result;
FILE* pipe = popen(cmd.c_str(), "r");
if (!pipe) {
throw std::runtime_error("Failed to execute command: " + cmd);
}
while (fgets(buffer.data(), buffer.size(), pipe)) {
result += buffer.data();
}
pclose(pipe);
return result;
}
/**
* @brief Send a file path to Kakoune session
*
* @param path The file path to send
*/
void sendToKakoune(const std::string& path) {
std::string cmd = "kak -p " + KAK_SESSION;
FILE* kak_pipe = popen(cmd.c_str(), "w");
if (!kak_pipe) {
throw std::runtime_error("Failed to start Kakoune session: " + cmd);
}
fprintf(kak_pipe, "%s\n", path.c_str());
pclose(kak_pipe);
}
/**
* @brief Main function that handles the file processing and editor communication
*
* @return int Exit code (0 for success, non-zero for failure)
*/
int main() {
try {
// Check if NNN selection file exists
if (!std::filesystem::exists(NNN_SEL)) {
throw std::runtime_error("NNN selection file does not exist: " + NNN_SEL);
}
// Open and read NNN selection file
std::ifstream sel_file(NNN_SEL);
if (!sel_file.is_open()) {
throw std::runtime_error("Failed to open NNN selection file: " + NNN_SEL);
}
std::string path;
while (std::getline(sel_file, path, sel_file.widen('\0'))) {
// Skip empty lines
if (path.empty()) continue;
// Check for spaces and escape them if necessary
bool contains_space = false;
for (char c : path) {
if (c == ' ') {
contains_space = true;
break;
}
}
std::string processed_path = contains_space ? escapeSpaces(path) : path;
// Construct and execute Kakoune evaluation command
std::string squote = "\'" + processed_path + "\'";
std::string cmd = "printf '%s' \"" + KAK_EVAL + squote + "\"";
std::string output = executeCommand(cmd);
// Send the file path to Kakoune session
sendToKakoune(output);
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1; // Indicate error
}
return 0; // Success
}
>>> Change the below Raku programming language snippet into a C++ version 17 programming language script with detailed documentation strings.
ollama/cogito:70b
Model
architecture llama
parameters 70.6B
context length 131072
embedding length 8192
quantization Q4_K_M
Capabilities
completion
tools
License
Apache License
Version 2.0, January 2004
...
NNN like Kakoune is democratic in its plugin system. export NNN_PLUG='...'
NEED TO CHANGE TO YOUR PATH: private static final String NNN_SEL = "/home/USER/.config/nnn/.selection";
NNNSendToKakoune.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.file.Path;
import java.util.stream.Stream;
import static java.lang.System.err;
import static java.nio.file.Files.exists;
import static java.nio.file.Files.lines;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
/**
* Utility class to send file selections from NNN (a terminal file browser)
* into Kakoune editor sessions. This handles reading the selection file,
* processing paths, and sending them to an existing Kakoune session.
*/
public final class NNNSendToKakoune {
/** Path to NNN's selection file containing selected files/paths. */
private static final String NNN_SEL = "/home/USER/.config/nnn/.selection";
/** Command used for executing shell commands in the system. */
private static final String PRINTF = "printf";
/** Kakoune command to evaluate client commands safely. */
private static final String KAK_EVAL = "evaluate-commands -try-client";
/** Default Kakoune client ID. */
private static final String KAK_CLIENT = "client0";
/** Command for opening files in Kakoune. */
private static final String KAK_EDIT = "edit -existing";
/** Main Kakoune command executable name. */
private static final String KAK_CMD = "kak";
/** Flag to attach to existing Kakoune session. */
private static final String KAK_FLAG = "-p";
/** Name of the Kakoune session to connect to. */
private static final String KAK_SESSION = "nnn-session";
/**
* Main entry point that processes NNN selections and sends them to Kakoune.
* Reads paths from {@value #NNN_SEL}, processes each path, and forwards
* them to an existing Kakoune session.
*
* @throws RuntimeException if the selection file is not found or processing fails
*/
static void main() throws RuntimeException {
try {
final Path nnn_sel = Path.of(NNN_SEL);
if (exists(nnn_sel)) {
try (Stream<String> line = lines(nnn_sel)) {
line.forEach(NNNSendToKakoune::nullCharacter);
}
}
else throw new RuntimeException("NNN selection file not found: %s".formatted(NNN_SEL));
} catch (Exception e) { err.printf("Error: %s%n", e.getMessage()); }
}
/**
* Processes a single path string by escaping spaces and sending it to Kakoune.
*
* @param path The file/directory path to process
* @throws RuntimeException if command execution or communication with Kakoune fails
*/
private static void processPath(final String path) throws RuntimeException {
if (!path.isEmpty()) {
try {
String processedPath = containsSpace(path) ? escapeSpaces(path) : path;
String kakCmd = "%s %s %s '%s'".formatted(KAK_EVAL, KAK_CLIENT, KAK_EDIT, processedPath);
String output = executeCommand(kakCmd);
sendToKakoune(output);
} catch (Exception e) { err.printf("Error processing path: %s%n", e.getMessage()); }
}
}
/**
* Splits a line of NNN selection data and processes each path.
*
* @param paths Selection line containing null-separated paths
*/
private static void nullCharacter(final String paths) {
stream(paths.split("\0"))
.sequential()
.forEach(NNNSendToKakoune::processPath);
}
/**
* Checks if the given string contains spaces that need escaping.
*
* @param str The string to check for spaces
* @return true if the string contains at least one space character
*/
private static boolean containsSpace(final String str) {
return str.contains(" ");
}
/**
* Escapes spaces in a path by replacing them with "\ ".
*
* @param path The path containing spaces to escape
* @return The escaped path string
*/
private static String escapeSpaces(final String path) {
return path.replace(" ", "\\ ");
}
/**
* Executes a system command and returns its output.
*
* @param kakCmd The command string to execute via printf
* @return Command execution output as a single string
* @throws IOException if process input/output streams fail
* @throws InterruptedException if thread is interrupted while waiting for process
* @throws RuntimeException if communication with system fails
*/
private static String executeCommand(final String kakCmd) throws IOException, InterruptedException, RuntimeException {
Process process = new ProcessBuilder(PRINTF, kakCmd).start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String output = reader.lines().map("%s"::formatted).collect(joining());
int exitCode = process.waitFor();
if (0 != exitCode) throw new RuntimeException("Command failed with exit code: %d".formatted(exitCode));
return output;
}
}
/**
* Sends processed data to an existing Kakoune session.
*
* @param output The command string to send to Kakoune
* @throws IOException if writing to process stdin fails
* @throws InterruptedException if thread is interrupted while waiting for process
* @throws RuntimeException if communication with Kakoune fails
*/
private static void sendToKakoune(final String output) throws IOException, InterruptedException, RuntimeException {
Process process = new ProcessBuilder(KAK_CMD, KAK_FLAG, KAK_SESSION).start();
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(process.getOutputStream()))) {
writer.write(output);
writer.newLine();
writer.flush();
writer.close();
int exitCode = process.waitFor();
if (0 != exitCode) throw new RuntimeException("Failed to send path to Kakoune: %s".formatted(output));
}
}
}
Launch the catapults!
>>> Please add comprehensive JavaDoc comments to this Java code.
...
ollama/cogito:32b
Model
architecture qwen2
parameters 32.8B
context length 131072
embedding length 5120
quantization Q4_K_M
Capabilities
completion
tools
License
Apache License
Version 2.0, January 2004
...