lf: Unexpected Space Added After First Unquoted '$' in CLI Commands

Bug Description

When inputting command line commands in lf, the application appends a space after the first ‘$’ without quoting the dollar sign. This results in unexpected behavior when handling arguments.

For example, when I input:

:echo $A $B $C $D

lf output:

$ A $B $C $D

(NOTE: space before ‘A’)

When I input:

:echo "$A" $B $C $D

lf output:

$A $ B $C $D

(NOTE: space before ‘B’)

Steps to Reproduce

  1. Open lf.
  2. Enter the command :echo $A $B $C $D.
  3. Check the output of the command.

Expected Behavior

The output of the command :echo $A $B $C $D should display $A $B $C $D without any additional spaces.

Environment

Operating System: macOS Monterey 12.6.1 lf version: r29

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Comments: 15 (3 by maintainers)

Most upvoted comments

lf doesn’t expand tildes when parsing commands (there are a few exceptions such as source and cd for convenience, but in general tildes are treated as a regular character), so I would find it confusing if variables were expanded and tildes weren’t.

I actually tried to expand tildes in #1246, but this idea was rejected and instead I ended up changing the code to have the autocompletion expand tildes before the command is parsed. I wonder if it’s possible to apply this to environment variables but it could become a problem - suppose if you have two environment variables $ABC and $ABCDEF, does typing $ABC<tab> expand to the value of $ABC or the text $ABCDEF?

One other thing I’m uncertain about is how users normally use lf. I feel that it’s supposed to be an interactive tool (you can create commands but usually these are mapped to keybindings). Rarely would you need to manually type commands into the command line, and if you really do need a powerful interpreter that can understand environment variables, why not just start a shell (and perhaps use shell scripts over lf commands)? I’m still leaning towards the opinion that the command line in lf is supposed to be a convenience for entering simple commands, as opposed to being a fully-fledged shell.

Of course, this opinion is biased towards my own usage of lf, and other users might think differently. I’m still not entirely what should be done here, perhaps we can leave this issue open and see if any other users are interested in this discussion.

The statement about $ not having any special meaning unless it’s the first character is mostly true. There are a few cases like :cmd d $date and :map d $date where $ is still treated specially though.

Your use case of specifying environment variables to pass to the shell and then manually expanding them using envsubst is not something I have seen before. If you’re only need to use $HOME, then probably it’s better for you to type :CD ~/<Tab>, since lf can actually expand ~ for completion purposes (you would still need to manually expand ~ afterwards).

Otherwise if this isn’t sufficient for you, then you can try the patch in https://github.com/gokcehan/lf/issues/1219#issuecomment-1533992305, but I would leave it up to @gokcehan to say whether it really is a good idea or not.

BTW the lf autocompletion already does escape special characters, but only whitespace, \, ; and # are treated as special characters. This can be changed though:

diff --git a/complete.go b/complete.go
index 8900518..8bf6fcc 100644
--- a/complete.go
+++ b/complete.go
@@ -325,7 +325,7 @@ func matchFile(s string) (matches []string, longest []rune) {
 
 		name = strings.ToLower(escape(f.Name()))
 		_, last := filepath.Split(s)
-		if !strings.HasPrefix(name, strings.ToLower(last)) {
+		if !strings.HasPrefix(name, strings.ToLower(escape(last))) {
 			continue
 		}
 
diff --git a/misc.go b/misc.go
index 451b760..b1d1e2c 100644
--- a/misc.go
+++ b/misc.go
@@ -55,7 +55,7 @@ func runeSliceWidthRange(rs []rune, beg, end int) []rune {
 func escape(s string) string {
 	buf := make([]rune, 0, len(s))
 	for _, r := range s {
-		if unicode.IsSpace(r) || r == '\\' || r == ';' || r == '#' {
+		if unicode.IsSpace(r) || strings.ContainsRune(`\;#$`, r) {
 			buf = append(buf, '\\')
 		}
 		buf = append(buf, r)
@@ -70,7 +70,7 @@ func unescape(s string) string {
 	buf := make([]rune, 0, len(s))
 	for _, r := range s {
 		if esc {
-			if !unicode.IsSpace(r) && r != '\\' && r != ';' && r != '#' {
+			if !unicode.IsSpace(r) && !strings.ContainsRune(`\;#$`, r) {
 				buf = append(buf, '\\')
 			}
 			buf = append(buf, r)

Now typing :edit $<Tab> will autocomplete to :edit \$ab, which should successfully open your $ab file.

This scenario is somewhat contrived though, I think it might be better to consult with @gokcehan first before proposing such a change. Also we would need to identify the full set of special characters and update the unit tests too.