Add and integrate a script to execute mc.ext.ini

Tsk, tsk, parasiting on what we wanted to replace.

macOS is annoying to port to.

Unfortunately, this script is also very slow, for some reason.
This commit is contained in:
Přemysl Eric Janouch 2024-12-28 00:28:22 +01:00
parent 85b2d8a2ee
commit 3075d47aeb
Signed by: p
GPG Key ID: A0420B94F92B9493
7 changed files with 264 additions and 10 deletions

View File

@ -28,8 +28,10 @@ target_compile_definitions (${PROJECT_NAME} PUBLIC
include (GNUInstallDirs)
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
install (PROGRAMS ${PROJECT_NAME}-install DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES sdn.1 sdn-install.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
install (PROGRAMS ${PROJECT_NAME}-install ${PROJECT_NAME}-view
DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES sdn.1 sdn-install.1 sdn-view.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Directory navigator")

8
NEWS
View File

@ -1,3 +1,11 @@
Unreleased
* Added an sdn-view script that can process Midnight Commander mc.ext.ini files
and apply matching filters; this script has been made the default F3 binding,
while the original direct pager invocation has been moved to F13 (which also
reflects Midnight Commander)
1.0.0 (2024-12-21)
* Initial release

View File

@ -1,6 +1,6 @@
.Dd October 27, 2020
.Dt SDN-INSTALL 1
.Os Linux
.Os
.Sh NAME
.Nm sdn-install
.Nd integrate sdn with the shell

211
sdn-view Executable file
View File

@ -0,0 +1,211 @@
#!/bin/sh -e
# sdn-view: a viewer for sdn that makes use of Midnight Commander configuration
# to make more kinds of files directly viewable
if [ "$#" -ne 1 ]
then
echo "Usage: $0 FILE" >&2
exit 2
fi
# This handles both MC_DATADIR and odd installation locations.
datadir=
if command -v mc >/dev/null
then datadir=$(mc --datadir | sed 's/ (.*)$//')
fi
export SDN_VIEW_CONFIG=
for dir in "$HOME"/.config/mc "$datadir" /etc/mc
do
if [ -n "$dir" -a -f "$dir/mc.ext.ini" ]
then
SDN_VIEW_CONFIG=$dir/mc.ext.ini
break
fi
done
export PAGER=${PAGER:-less}
export MC_EXT_FILENAME=$(realpath "$1")
export MC_EXT_BASENAME=$(basename "$1")
export MC_EXT_CURRENTDIR=$(dirname "$1")
export SDN_VIEW_TYPE=$(file -bz "$1")
process() (awk -f - <<'EOF'
BEGIN {
if (!(Config = ENVIRON["SDN_VIEW_CONFIG"]))
exit
Verb = "View"
Section = ""
while ((getline < Config) > 0) {
if (/^\s*(#.*)?$/) {
# Skip.
} else if (/^\[[^]]+\]$/) {
Sections[++SectionsLen] = Section = substr($0, 2, length($0) - 2)
} else if (/^[^=]+=[^=]*$/) {
split($0, kv, "=")
Keys[Section, kv[1]] = kv[2]
}
}
Type = ENVIRON["SDN_VIEW_TYPE"]
Path = ENVIRON["MC_EXT_FILENAME"]
Basename = ENVIRON["MC_EXT_BASENAME"]
Dirname = ENVIRON["MC_EXT_CURRENTDIR"]
for (i = 1; i <= SectionsLen; i++) {
if (Sections[i] == "mc.ext.ini" ||
Sections[i] == "Default" ||
Sections[i] ~ /^Include[\/]/)
continue
try(Sections[i])
}
# Not attempting any inclusions here.
print expand_command(Keys["Default", Verb])
}
function try(section, pair, a, key, full, include) {
for (pair in Keys) {
split(pair, a, SUBSEP)
if (a[1] == section)
full[a[2]] = Keys[pair]
}
if ("Include" in full) {
delete full["Open"]
delete full["View"]
delete full["Edit"]
include = "Include/" full["Include"]
for (pair in Keys) {
split(pair, a, SUBSEP)
if (a[1] == include)
full[a[2]] = Keys[pair]
}
}
if (ENVIRON["SDN_VIEW_DEBUG"]) {
print "[" section "]" > "/dev/stderr"
for (key in full)
print " " key ": " full[key] > "/dev/stderr"
}
if (Verb in full && section_matches(full, Type, Basename)) {
print expand_command(full[Verb])
exit
}
}
function shell_escape(string) {
gsub(/'/, "'\\''", string)
return "'" string "'"
}
function expand_command(cmd, toview, out, seq, argument, value, a, pipe) {
out = ""
while (match(cmd, /%[a-zA-Z]*\{[^}]*\}|%[a-zA-Z]+|%%/)) {
out = out substr(cmd, 1, RSTART - 1)
seq = substr(cmd, RSTART + 1, RLENGTH - 1)
cmd = substr(cmd, RSTART + RLENGTH)
argument = ""
if (match(seq, /\{.*\}$/)) {
argument = substr(seq, RSTART + 1, RLENGTH - 2)
seq = substr(seq, 1, RSTART - 1)
}
if (seq == "%") {
out = out "%"
} else if (seq == "p") {
out = out shell_escape(Basename)
} else if (seq == "f") {
out = out shell_escape(Path)
} else if (seq == "d") {
out = out shell_escape(Dirname)
} else if (seq == "view") {
toview = 1
sub(/^ +/, "", cmd)
split(argument, a, /,/)
for (value in a) {
if (a[value] == "hex")
pipe = pipe " | od -t x1"
# more(1) and less(1) either ignore or display this:
#if (a[value] == "nroff")
# pipe = pipe " | col -b"
}
} else if (seq == "var") {
value = ""
if (!match(argument, /:.*/)) {
if (argument in ENVIRON)
value = ENVIRON[argument]
} else {
value = substr(argument, RSTART + 1)
argument = substr(argument, 1, RSTART - 1)
if (argument in ENVIRON)
value = ENVIRON[argument]
}
out = out shell_escape(value)
} else if (seq == "") {
print Config ": prompting not supported" > "/dev/stderr"
return
} else {
print Config ": unsupported: %" seq > "/dev/stderr"
return
}
}
out = out cmd pipe
# While the processing is mostly generic for all verbs,
# we'd have to distinguish non-view commands in this AWK script's output.
if (!toview)
return
# In the case of out == "", we should just explicitly pass it to the pager,
# however it currently mixes with the case of "we can't use this View=".
return out
}
function section_matches(section, type, basename, value) {
if ("Directory" in section)
return 0
if ("Type" in section) {
value = section["Type"]
if ("TypeIgnoreCase" in section &&
section["TypeIgnoreCase"] == "true") {
type = tolower(type)
value = tolower(value)
}
gsub(/\\\\/, "\\", value)
gsub(/\\ /, " ", value)
if (type !~ value)
return 0
}
if ("Regex" in section) {
value = section["Regex"]
if ("RegexIgnoreCase" in section &&
section["RegexIgnoreCase"] == "true") {
basename = tolower(basename)
value = tolower(value)
}
gsub(/\\\\/, "\\", value)
return basename ~ value
} else if ("Shell" in section) {
value = section["Shell"]
if ("RegexIgnoreCase" in section &&
section["ShellIgnoreCase"] == "true") {
basename = tolower(basename)
value = tolower(value)
}
if (value !~ /^[.]/)
return value == basename
return length(basename) >= length(value) &&
substr(basename, length(basename) - length(value) + 1) == value
}
return type != ""
}
EOF
)
command=$(process)
if [ -z "$command" ]
then "$PAGER" -- "$MC_EXT_FILENAME"
else eval "$command" | "$PAGER"
fi

23
sdn-view.1 Normal file
View File

@ -0,0 +1,23 @@
.Dd December 28, 2024
.Dt SDN-VIEW 1
.Os
.Sh NAME
.Nm sdn-view
.Nd run Midnight Commander view configuration externally
.Sh SYNOPSIS
.Nm sdn-view
.Ar path
.Sh DESCRIPTION
.Nm
invokes
.Ev PAGER
or a fallback pager on the passed filename.
.Pp
If it succeeds in finding a
.Xr mc 1
.Pa mc.ext.ini
file, it will first process it, and apply any matching filter.
.Sh REPORTING BUGS
Use
.Lk https://git.janouch.name/p/sdn
to report bugs, request features, or submit pull requests.

6
sdn.1
View File

@ -1,7 +1,7 @@
\" https://mandoc.bsd.lv/man/roff.7.html#Sentence_Spacing
.Dd October 27, 2020
.Dt SDN 1
.Os Linux
.Os
.Sh NAME
.Nm sdn
.Nd directory navigator
@ -68,8 +68,8 @@ and you can use the
.Xr dircolors 1
utility to initialize this variable.
.It Ev PAGER
The viewer program to be launched by the F3 key binding as well as to show
the internal help message.
The viewer program to be launched by the F3 and F13 key bindings as well as
to show the internal help message.
If none is set, it defaults to
.Xr less 1 .
.It Ev VISUAL , Ev EDITOR

18
sdn.cpp
View File

@ -428,7 +428,8 @@ enum { ALT = 1 << 24, SYM = 1 << 25 }; // Outside the range of Unicode
#define CTRL(char) ((char) == '?' ? 0x7f : (char) & 0x1f)
#define ACTIONS(XX) XX(NONE) XX(HELP) XX(QUIT) XX(QUIT_NO_CHDIR) \
XX(CHOOSE) XX(CHOOSE_FULL) XX(VIEW) XX(EDIT) XX(SORT_LEFT) XX(SORT_RIGHT) \
XX(CHOOSE) XX(CHOOSE_FULL) XX(VIEW_RAW) XX(VIEW) XX(EDIT) \
XX(SORT_LEFT) XX(SORT_RIGHT) \
XX(UP) XX(DOWN) XX(TOP) XX(BOTTOM) XX(HIGH) XX(MIDDLE) XX(LOW) \
XX(PAGE_PREVIOUS) XX(PAGE_NEXT) XX(SCROLL_UP) XX(SCROLL_DOWN) XX(CENTER) \
XX(CHDIR) XX(PARENT) XX(GO_START) XX(GO_HOME) \
@ -451,7 +452,8 @@ static map<wint_t, action> g_normal_actions {
{ALT | '\r', ACTION_CHOOSE_FULL}, {ALT | KEY (ENTER), ACTION_CHOOSE_FULL},
{'\r', ACTION_CHOOSE}, {KEY (ENTER), ACTION_CHOOSE},
{KEY (F (1)), ACTION_HELP}, {'h', ACTION_HELP},
{KEY (F (3)), ACTION_VIEW}, {KEY (F (4)), ACTION_EDIT},
{KEY (F (3)), ACTION_VIEW}, {KEY (F (13)), ACTION_VIEW_RAW},
{KEY (F (4)), ACTION_EDIT},
{'q', ACTION_QUIT}, {ALT | 'q', ACTION_QUIT_NO_CHDIR},
// M-o ought to be the same shortcut the navigator is launched with
{ALT | 'o', ACTION_QUIT},
@ -1015,12 +1017,17 @@ fun run_program (initializer_list<const char *> list, const string &filename) {
update ();
}
fun view (const string &filename) {
fun view_raw (const string &filename) {
// XXX: we cannot realistically detect that the pager hasn't made a pause
// at the end of the file, so we can't ensure all contents have been seen
run_program ({(const char *) getenv ("PAGER"), "less", "cat"}, filename);
}
fun view (const string &filename) {
run_program ({(const char *) getenv ("SDN_VIEWER"), "sdn-view",
(const char *) getenv ("PAGER"), "less", "cat"}, filename);
}
fun edit (const string &filename) {
run_program ({(const char *) getenv ("VISUAL"),
(const char *) getenv ("EDITOR"), "vi"}, filename);
@ -1445,8 +1452,11 @@ fun handle (wint_t c) -> bool {
case ACTION_CHOOSE:
choose (current);
break;
case ACTION_VIEW:
case ACTION_VIEW_RAW:
// Mimic mc, it does not seem sensible to page directories
(is_directory ? change_dir : view_raw) (current.filename);
break;
case ACTION_VIEW:
(is_directory ? change_dir : view) (current.filename);
break;
case ACTION_EDIT: