Skip to content

Commit

Permalink
Merge pull request #20 from RossComputerGuy/feat/zig-repl
Browse files Browse the repository at this point in the history
feat(libcmd): add zig based readline
  • Loading branch information
RossComputerGuy authored Feb 8, 2025
2 parents a4f7429 + 4233f8b commit 06a2532
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 6 deletions.
5 changes: 5 additions & 0 deletions src/libcmd/libcmd.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub const repl = @import("repl.zig");

comptime {
_ = repl;
}
25 changes: 24 additions & 1 deletion src/libcmd/meson.build
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
project('nix-cmd', 'cpp',
project('nix-cmd', 'cpp', 'c',
version : files('.zix-version'),
default_options : [
'cpp_std=c++2a',
Expand All @@ -10,6 +10,8 @@ project('nix-cmd', 'cpp',
license : 'LGPL-2.1-or-later',
)

fs = import('fs')

cxx = meson.get_compiler('cpp')

subdir('nix-meson-build-support/deps-lists')
Expand Down Expand Up @@ -49,6 +51,12 @@ elif readline_flavor == 'readline'
1,
description: 'Use readline instead of editline',
)
elif readline_flavor == 'zig'
configdata.set(
'USE_ZIG_REPL',
1,
description: 'Use zig implementation for repl',
)
else
error('illegal editline flavor', readline_flavor)
endif
Expand Down Expand Up @@ -113,10 +121,25 @@ headers = [config_h] + files(
'repl.hh',
)

subdir('nix-meson-build-support/zig')

sources += custom_target(
'zig build-obj',
command: [zig, 'build-lib', '-femit-h=@OUTDIR@/libcmd-zig.h', '-femit-bin=@OUTPUT@', '-ofmt=c', '-lc', zig_args, '@INPUT@'],
output: 'libcmd-zig.c',
input: 'libcmd.zig',
depend_files: [
'repl.zig',
],
)

include_dirs += fs.parent(fs.parent(zig.full_path())) / 'lib' / 'zig'

this_library = library(
'nixcmd',
sources,
dependencies : deps_public + deps_private + deps_other,
include_directories : include_dirs,
prelink : true, # For C++ static initializers
install : true,
)
Expand Down
2 changes: 1 addition & 1 deletion src/libcmd/meson.options
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ option(
option(
'readline-flavor',
type : 'combo',
choices : ['editline', 'readline'],
choices : ['editline', 'readline', 'zig'],
value : 'editline',
description : 'Which library to use for nice line editing with the Nix language REPL',
)
17 changes: 15 additions & 2 deletions src/libcmd/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@
#
# - editline (default)
# - readline
readlineFlavor ? if stdenv.hostPlatform.isWindows then "readline" else "editline",
readlineFlavor ?
if stdenv.hostPlatform.isWindows then
"readline"
else if stdenv.hostPlatform.isLinux then
"zig"
else
"editline",
}:

let
Expand All @@ -51,10 +57,17 @@ mkMesonLibrary (finalAttrs: {
./meson.options
(fileset.fileFilter (file: file.hasExt "cc") ./.)
(fileset.fileFilter (file: file.hasExt "hh") ./.)
(fileset.fileFilter (file: file.hasExt "zig") ./.)
];

buildInputs = [
({ inherit editline readline; }.${readlineFlavor})
(
{
inherit editline readline;
zig = null;
}
.${readlineFlavor}
)
] ++ lib.optional enableMarkdown lowdown;

propagatedBuildInputs = [
Expand Down
14 changes: 12 additions & 2 deletions src/libcmd/repl-interacter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <readline/history.h>
#include <readline/readline.h>
#else
#ifdef USE_ZIG_REPL
extern "C" char* readline(const char* prompt);
#else
// editline < 1.15.2 don't wrap their API for C++ usage
// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
// This results in linker errors due to to name-mangling of editline C symbols.
Expand All @@ -13,6 +16,7 @@ extern "C" {
#include <editline.h>
}
#endif
#endif

#include "signals.hh"
#include "finally.hh"
Expand Down Expand Up @@ -107,20 +111,24 @@ static int listPossibleCallback(char * s, char *** avp)
ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleterMixin * repl)
{
// Allow nix-repl specific settings in .inputrc
#ifndef USE_ZIG_REPL
rl_readline_name = "nix-repl";
#endif
try {
createDirs(dirOf(historyFile));
} catch (SystemError & e) {
logWarning(e.info());
}
#ifndef USE_READLINE
#if !defined(USE_READLINE) && !defined(USE_ZIG_REPL)
el_hist_size = 1000;
#endif
#ifndef USE_ZIG_REPL
read_history(historyFile.c_str());
#endif
auto oldRepl = curRepl;
curRepl = repl;
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
#ifndef USE_READLINE
#if !defined(USE_READLINE) && !defined(USE_ZIG_REPL)
rl_set_complete_func(completionCallback);
rl_set_list_possib_func(listPossibleCallback);
#endif
Expand Down Expand Up @@ -200,7 +208,9 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT

ReadlineLikeInteracter::~ReadlineLikeInteracter()
{
#ifndef USE_ZIG_REPL
write_history(historyFile.c_str());
#endif
}

};
31 changes: 31 additions & 0 deletions src/libcmd/repl.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const std = @import("std");

var has_prompt = true;

pub fn readline(prompt: [*:0]const u8) callconv(.C) ?[*:0]const u8 {
const stdout = std.io.getStdOut();
if (!has_prompt) {
stdout.writeAll(prompt[0..std.mem.len(prompt)]) catch @panic("Failed to write prompt");
has_prompt = true;
}

const stdin = std.io.getStdIn();

var line = std.ArrayList(u8).init(std.heap.c_allocator);
defer line.deinit();

stdin.reader().readUntilDelimiterArrayList(&line, '\n', std.math.maxInt(usize)) catch |err| switch (err) {
error.StreamTooLong => {
std.log.err("readline(\"{s}\") failed {}", .{ prompt[0..std.mem.len(prompt)], err });
return null;
},
else => return null,
};

has_prompt = line.items.len > 0;
return line.toOwnedSliceSentinel(0) catch null;
}

comptime {
@export(&readline, .{ .name = "readline" });
}

0 comments on commit 06a2532

Please sign in to comment.