@@ -3303,7 +3303,7 @@ contains |
| 3303 | 3303 | var_prefix = prefix_with_dollar(2:) |
| 3304 | 3304 | |
| 3305 | 3305 | ! Get variable names from environment (exported + inherited) |
| 3306 | | - env_alloc = execute_and_capture('env 2>/dev/null | cut -d= -f1') |
| 3306 | + env_alloc = execute_and_capture('env 2>/dev/null | cut -d= -f1 | tr ' // "'" // char(92) // 'n' // "' ' '") |
| 3307 | 3307 | env_output = env_alloc(:min(len(env_output), len(env_alloc))) |
| 3308 | 3308 | if (allocated(env_alloc)) deallocate(env_alloc) |
| 3309 | 3309 | |
@@ -3532,12 +3532,13 @@ contains |
| 3532 | 3532 | ! Trailing / on directory forces ls to list contents (not the symlink itself on macOS) |
| 3533 | 3533 | ! When pattern is non-empty, filter with grep to avoid buffer overflow on large directories |
| 3534 | 3534 | ! Use tr to convert newlines to spaces for easier parsing |
| 3535 | | - ! Keep newlines as delimiters to preserve spaces in filenames |
| 3535 | + ! Filter with grep when pattern exists to limit output size |
| 3536 | 3536 | if (pattern_len > 0) then |
| 3537 | 3537 | ls_command = 'ls -1aF "' // trim(expanded_dir) // '/" 2>/dev/null | grep -i "^' // & |
| 3538 | | - trim(pattern) // '"' |
| 3538 | + trim(pattern) // '" | tr ' // "'" // char(92) // 'n' // "' ' '" |
| 3539 | 3539 | else |
| 3540 | | - ls_command = 'ls -1aF "' // trim(expanded_dir) // '/" 2>/dev/null' |
| 3540 | + ls_command = 'ls -1aF "' // trim(expanded_dir) // '/" 2>/dev/null | tr ' // & |
| 3541 | + "'" // char(92) // 'n' // "' ' '" |
| 3541 | 3542 | end if |
| 3542 | 3543 | |
| 3543 | 3544 | ! Debug output |
@@ -3670,9 +3671,8 @@ contains |
| 3670 | 3671 | num_entries = 0 |
| 3671 | 3672 | pos = 1 |
| 3672 | 3673 | do while (pos <= output_len) |
| 3673 | | - ! Skip newline/whitespace delimiters |
| 3674 | | - do while (pos <= output_len .and. (output(pos:pos) == char(10) .or. & |
| 3675 | | - output(pos:pos) == char(13) .or. output(pos:pos) == char(9))) |
| 3674 | + ! Skip whitespace |
| 3675 | + do while (pos <= output_len .and. (output(pos:pos) == ' ' .or. output(pos:pos) == char(9))) |
| 3676 | 3676 | pos = pos + 1 |
| 3677 | 3677 | end do |
| 3678 | 3678 | |
@@ -3680,9 +3680,8 @@ contains |
| 3680 | 3680 | |
| 3681 | 3681 | start = pos |
| 3682 | 3682 | |
| 3683 | | - ! Find end of entry (newline delimiter preserves spaces in filenames) |
| 3684 | | - do while (pos <= output_len .and. output(pos:pos) /= char(10) .and. & |
| 3685 | | - output(pos:pos) /= char(13) .and. output(pos:pos) /= char(9)) |
| 3683 | + ! Find end of entry (space-separated, since execute_and_capture converts newlines to spaces) |
| 3684 | + do while (pos <= output_len .and. output(pos:pos) /= ' ') |
| 3686 | 3685 | pos = pos + 1 |
| 3687 | 3686 | end do |
| 3688 | 3687 | |
@@ -3701,9 +3700,8 @@ contains |
| 3701 | 3700 | count_pass = 0 |
| 3702 | 3701 | pos = 1 |
| 3703 | 3702 | do while (pos <= output_len .and. count_pass < num_entries) |
| 3704 | | - ! Skip newline/whitespace delimiters |
| 3705 | | - do while (pos <= output_len .and. (output(pos:pos) == char(10) .or. & |
| 3706 | | - output(pos:pos) == char(13) .or. output(pos:pos) == char(9))) |
| 3703 | + ! Skip whitespace |
| 3704 | + do while (pos <= output_len .and. (output(pos:pos) == ' ' .or. output(pos:pos) == char(9))) |
| 3707 | 3705 | pos = pos + 1 |
| 3708 | 3706 | end do |
| 3709 | 3707 | |
@@ -3711,9 +3709,8 @@ contains |
| 3711 | 3709 | |
| 3712 | 3710 | start = pos |
| 3713 | 3711 | |
| 3714 | | - ! Find end of entry (newline delimiter) |
| 3715 | | - do while (pos <= output_len .and. output(pos:pos) /= char(10) .and. & |
| 3716 | | - output(pos:pos) /= char(13) .and. output(pos:pos) /= char(9)) |
| 3712 | + ! Find end of entry |
| 3713 | + do while (pos <= output_len .and. output(pos:pos) /= ' ') |
| 3717 | 3714 | pos = pos + 1 |
| 3718 | 3715 | end do |
| 3719 | 3716 | |
@@ -3894,12 +3891,34 @@ contains |
| 3894 | 3891 | ! No completions found |
| 3895 | 3892 | return |
| 3896 | 3893 | else if (num_completions == 1) then |
| 3897 | | - ! Single completion - add prefix back (preserve spacing) |
| 3898 | | - if (last_space_pos > 0) then |
| 3899 | | - completed_line = prefix_part(:last_space_pos) // trim(completions(1)) |
| 3900 | | - else |
| 3901 | | - completed_line = trim(completions(1)) |
| 3902 | | - end if |
| 3894 | + ! Single completion - reconstruct with proper quoting |
| 3895 | + block |
| 3896 | + character(len=1) :: quote_char |
| 3897 | + integer :: lw_len |
| 3898 | + |
| 3899 | + lw_len = len_trim(last_word) |
| 3900 | + quote_char = ' ' |
| 3901 | + |
| 3902 | + ! Check if last_word starts with a quote |
| 3903 | + if (lw_len > 0 .and. (last_word(1:1) == "'" .or. last_word(1:1) == '"')) then |
| 3904 | + quote_char = last_word(1:1) |
| 3905 | + end if |
| 3906 | + |
| 3907 | + ! Completions already include the full path from scan_directory. |
| 3908 | + ! Just wrap in quotes if the original word was quoted. |
| 3909 | + if (quote_char /= ' ') then |
| 3910 | + if (last_space_pos > 0) then |
| 3911 | + completed_line = prefix_part(:last_space_pos) // quote_char // & |
| 3912 | + trim(completions(1)) // quote_char |
| 3913 | + else |
| 3914 | + completed_line = quote_char // trim(completions(1)) // quote_char |
| 3915 | + end if |
| 3916 | + else if (last_space_pos > 0) then |
| 3917 | + completed_line = prefix_part(:last_space_pos) // trim(completions(1)) |
| 3918 | + else |
| 3919 | + completed_line = trim(completions(1)) |
| 3920 | + end if |
| 3921 | + end block |
| 3903 | 3922 | completed = .true. |
| 3904 | 3923 | else |
| 3905 | 3924 | ! Multiple completions |