Add and integrate sdn-open
All checks were successful
Alpine 3.22 Success
Arch Linux AUR Success
OpenBSD 7.8 Success

Originally I thought that not supporting %cd would be an issue,
making this kind of utility unclean.

It turns out the desire to launch xdg-open quickly is stronger.
This commit is contained in:
2025-11-20 20:49:14 +01:00
parent 977d1a7120
commit 3e39cc5660
6 changed files with 96 additions and 10 deletions

View File

@@ -33,9 +33,9 @@ include (GNUInstallDirs)
# sdn-mc-ext should be in libexec, but we prefer it in PATH. # sdn-mc-ext should be in libexec, but we prefer it in PATH.
install (TARGETS sdn sdn-mc-ext install (TARGETS sdn sdn-mc-ext
DESTINATION ${CMAKE_INSTALL_BINDIR}) DESTINATION ${CMAKE_INSTALL_BINDIR})
install (PROGRAMS sdn-install sdn-view install (PROGRAMS sdn-install sdn-open sdn-view
DESTINATION ${CMAKE_INSTALL_BINDIR}) DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES sdn.1 sdn-install.1 sdn-view.1 install (FILES sdn.1 sdn-install.1 sdn-open.1 sdn-view.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})

View File

@@ -1,4 +1,4 @@
Copyright (c) 2017 - 2024, Přemysl Eric Janouch <p@janouch.name> Copyright (c) 2017 - 2025, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted. purpose with or without fee is hereby granted.

5
NEWS
View File

@@ -5,7 +5,7 @@ Unreleased
- + and - adjust the selection using shell globs; - + and - adjust the selection using shell globs;
- t and T insert the selection into the external command line - t and T insert the selection into the external command line
in relative or absolute form, respectively; in relative or absolute form, respectively;
- Enter is like t but enters directories, and M-Enter is synonymous to t; - Enter is like t but enters directories;
- C-g or Escape clear the selection, similarly to the editor. - C-g or Escape clear the selection, similarly to the editor.
* Added an sdn-view script that can process Midnight Commander mc.ext.ini files * Added an sdn-view script that can process Midnight Commander mc.ext.ini files
@@ -13,6 +13,9 @@ Unreleased
while the original direct pager invocation has been moved to F13 (which also while the original direct pager invocation has been moved to F13 (which also
reflects Midnight Commander) reflects Midnight Commander)
* Added an sdn-open script which does the same kind of processing as above
on top of xdg-open. This is what is now executed by M-Enter.
1.0.0 (2024-12-21) 1.0.0 (2024-12-21)

52
sdn-open Executable file
View File

@@ -0,0 +1,52 @@
#!/bin/sh -e
# sdn-open: an opener for sdn that makes use of Midnight Commander configuration
# to make more kinds of files directly openable
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
config=
for dir in "$HOME"/.config/mc "$datadir" /etc/mc
do
if [ -n "$dir" -a -f "$dir/mc.ext.ini" ]
then
config=$dir/mc.ext.ini
break
fi
done
# This is often used in %env{} expansion, so let's be on the same page.
export PAGER=${PAGER:-less}
export MC_EXT_FILENAME=$(realpath "$1")
export MC_EXT_BASENAME=$(basename "$1")
export MC_EXT_CURRENTDIR=$(dirname "$MC_EXT_FILENAME")
output=$(sdn-mc-ext <"$config" "$(file -Lbz "$1")" \
"$MC_EXT_FILENAME" "$MC_EXT_BASENAME" "$MC_EXT_CURRENTDIR" Open || :)
kind=$(echo "$output" | sed -n 1p)
command=$(echo "$output" | sed -n 2p)
case "$kind" in
cd)
# These mostly enter virtual filesystems, which we do not understand.
xdg-open "$MC_EXT_FILENAME"
;;
'')
if [ -n "$command" ]
then eval "$command"
else xdg-open "$MC_EXT_FILENAME"
fi
;;
*)
echo "Unsupported: $kind" >&2
exit 1
esac

23
sdn-open.1 Normal file
View File

@@ -0,0 +1,23 @@
.Dd November 20, 2025
.Dt SDN-OPEN 1
.Os
.Sh NAME
.Nm sdn-open
.Nd run Midnight Commander open configuration externally
.Sh SYNOPSIS
.Nm sdn-open
.Ar path
.Sh DESCRIPTION
.Nm
invokes
.Xr xdg-open 1
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 run any matching command instead.
.Sh REPORTING BUGS
Use
.Lk https://git.janouch.name/p/sdn
to report bugs, request features, or submit pull requests.

20
sdn.cpp
View File

@@ -1,7 +1,7 @@
// //
// sdn: simple directory navigator // sdn: simple directory navigator
// //
// Copyright (c) 2017 - 2024, Přemysl Eric Janouch <p@janouch.name> // Copyright (c) 2017 - 2025, Přemysl Eric Janouch <p@janouch.name>
// //
// Permission to use, copy, modify, and/or distribute this software for any // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted. // purpose with or without fee is hereby granted.
@@ -430,8 +430,8 @@ enum { ALT = 1 << 24, SYM = 1 << 25 }; // Outside the range of Unicode
#define CTRL(char) ((char) == '?' ? 0x7f : (char) & 0x1f) #define CTRL(char) ((char) == '?' ? 0x7f : (char) & 0x1f)
#define ACTIONS(XX) XX(NONE) XX(HELP) XX(QUIT) XX(QUIT_NO_CHDIR) \ #define ACTIONS(XX) XX(NONE) XX(HELP) XX(QUIT) XX(QUIT_NO_CHDIR) \
XX(ENTER) XX(CHOOSE) XX(CHOOSE_FULL) XX(VIEW_RAW) XX(VIEW) XX(EDIT) \ XX(ENTER) XX(OPEN) XX(CHOOSE) XX(CHOOSE_FULL) \
XX(SORT_LEFT) XX(SORT_RIGHT) \ XX(VIEW_RAW) XX(VIEW) XX(EDIT) XX(SORT_LEFT) XX(SORT_RIGHT) \
XX(SELECT) XX(DESELECT) XX(SELECT_TOGGLE) XX(SELECT_ABORT) \ XX(SELECT) XX(DESELECT) XX(SELECT_TOGGLE) XX(SELECT_ABORT) \
XX(UP) XX(DOWN) XX(TOP) XX(BOTTOM) XX(HIGH) XX(MIDDLE) XX(LOW) \ 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(PAGE_PREVIOUS) XX(PAGE_NEXT) XX(SCROLL_UP) XX(SCROLL_DOWN) XX(CENTER) \
@@ -453,7 +453,7 @@ static const char *g_action_names[] = {ACTIONS(XX)};
static map<wint_t, action> g_normal_actions { static map<wint_t, action> g_normal_actions {
{'\r', ACTION_ENTER}, {KEY (ENTER), ACTION_ENTER}, {'\r', ACTION_ENTER}, {KEY (ENTER), ACTION_ENTER},
{ALT | '\r', ACTION_CHOOSE}, {ALT | KEY (ENTER), ACTION_CHOOSE}, {ALT | '\r', ACTION_OPEN}, {ALT | KEY (ENTER), ACTION_OPEN},
{'t', ACTION_CHOOSE}, {'T', ACTION_CHOOSE_FULL}, {'t', ACTION_CHOOSE}, {'T', ACTION_CHOOSE_FULL},
{KEY (F (1)), ACTION_HELP}, {'h', ACTION_HELP}, {KEY (F (1)), ACTION_HELP}, {'h', ACTION_HELP},
{KEY (F (3)), ACTION_VIEW}, {KEY (F (13)), ACTION_VIEW_RAW}, {KEY (F (3)), ACTION_VIEW}, {KEY (F (13)), ACTION_VIEW_RAW},
@@ -1056,13 +1056,18 @@ fun run_program (initializer_list<const char *> list, const string &filename) {
update (); update ();
} }
fun sdn_open (const string &filename) {
run_program ({(const char *) getenv ("SDN_OPENER"), "sdn-open", "xdg-open"},
filename);
}
fun view_raw (const string &filename) { fun view_raw (const string &filename) {
// XXX: we cannot realistically detect that the pager hasn't made a pause // 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 // 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); run_program ({(const char *) getenv ("PAGER"), "less", "cat"}, filename);
} }
fun view (const string &filename) { fun sdn_view (const string &filename) {
run_program ({(const char *) getenv ("SDN_VIEWER"), "sdn-view", run_program ({(const char *) getenv ("SDN_VIEWER"), "sdn-view",
(const char *) getenv ("PAGER"), "less", "cat"}, filename); (const char *) getenv ("PAGER"), "less", "cat"}, filename);
} }
@@ -1525,12 +1530,15 @@ fun handle (wint_t c) -> bool {
case ACTION_ENTER: case ACTION_ENTER:
enter (current); enter (current);
break; break;
case ACTION_OPEN:
sdn_open (current.filename);
break;
case ACTION_VIEW_RAW: case ACTION_VIEW_RAW:
// Mimic mc, it does not seem sensible to page directories // Mimic mc, it does not seem sensible to page directories
(is_directory ? change_dir : view_raw) (current.filename); (is_directory ? change_dir : view_raw) (current.filename);
break; break;
case ACTION_VIEW: case ACTION_VIEW:
(is_directory ? change_dir : view) (current.filename); (is_directory ? change_dir : sdn_view) (current.filename);
break; break;
case ACTION_EDIT: case ACTION_EDIT:
edit (current.filename); edit (current.filename);