Subject: bug#76007: 31.0.50; Interpreter not correctly found when using nix-shell.

From: Malcolm Purvis <[email-masked]>
Date: Sun, 02 Feb 2025 22:46:44 +1100

In NixOS if you want to use a script that manages its own 
dependencies, you use a command called "nix-shell" or "nix shell" 
to launch the script.

The start of such a script looks like:

#! /usr/bin/env nix-shell
#! nix-shell -i bash -p imagemagick cowsay

or

#! /usr/bin/env nix
#! nix shell github:tomberek/-#perlWith.HTMLTokeParserSimple.LWP 
   --command perl -x
 
The OS interpreter is nix-shell and nix-shell launches the real 
interpreter to execute the rest of the script.

With these scripts, Emacs's current behaviour is as follows:

- nix-shell is not interpreter-mode-alist so Emacs looks at the 
  file's 
 extension:

 - If the file has an extension then the correct mode is used
 - If the file does not have an extension then Fundamental mode is 
 used 
   regardless of the script's actual language.

If the file is a shell script with the correct extension, sh-mode 
is used but sh-mode fails to guess the shell, leaving the variable 
sh-shell as 'nix-shell.

In most cases this means that some shell features are not enabled, 
but otherwise it is possible to edit the script.

However, lsp-mode's bash language support (lsp-bash.el) throws an 
error if sh-shell is not set to it's supported values ('sh and 
'bash), and as a result nix bash scripts fail.

The solution is to add a new regexp to 
auto-mode-interpreter-regexp that will detect a nix script and 
extract the real interpreter from the second line.  Nix shell can 
be invoked in a number of ways but this regexp matches the common 
forms. 

diff --git a/lisp/files.el b/lisp/files.el
index 5ff40c335d7..1f3ac1431d2 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -3389,6 +3389,7 @@ and `inhibit-local-variables-suffixes'.  If
    "\\(?:[^ \t\n]+=[^ \t\n]*[ \t]+\\)*"
    "\\)?"
    "\\)?"
+   "\\(?:\\(?:nix\\|nix-shell\\)\n#![ 
\t]+\\(?:nix\\|nix-shell\\).*\\(?:-i\\|--command\\)[ \t]+\\)?"
    ;; Group 2: interpreter.  "\\([^ \t\n]+\\)")
   "Regexp matching interpreters, for file mode determination.

Malcolm

In GNU Emacs 31.0.50 (build 2, x86_64-apple-darwin24.2.0, NS
appkit-2575.30 Version 15.2 (Build 24C101)) of 2025-01-11 built on
lamechWindowing system distributor 'Apple', version 10.3.2575
System Description:  macOS 15.2
 
Configured using:
 'configure 
 --enable-locallisppath=/usr/local/share/emacs/site-lisp 
 --infodir=/usr/local/Cellar/emacs-head@31/31.0.50_1/share/info/emacs 
 --prefix=/usr/local/Cellar/emacs-head@31/31.0.50_1 --without-x 
 --without-dbus --with-native-compilation --with-imagemagick 
 --with-modules --with-gnutls --with-rsvg --with-xml2 
 --with-xwidgets --with-webp --with-ns --disable-ns-self-contained 
 'CFLAGS=-O2 -march=native -pipe -DFD_SETSIZE=10000 
 -DDARWIN_UNLIMITED_SELECT' 'LDFLAGS=-L/usr/local/lib/gcc/14 
 -I/usr/local/opt/gcc/include -I/usr/local/opt/libgccjit/include 
 -I/usr/local/opt/gmp/include -I/usr/local/opt/jpeg/include''

-- 
		    Malcolm Purvis <[email-masked]>