@@ -3303,7 +3303,7 @@ contains |
| 3303 | var_prefix = prefix_with_dollar(2:) | 3303 | var_prefix = prefix_with_dollar(2:) |
| 3304 | | 3304 | |
| 3305 | ! Get variable names from environment (exported + inherited) | 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 | env_output = env_alloc(:min(len(env_output), len(env_alloc))) | 3307 | env_output = env_alloc(:min(len(env_output), len(env_alloc))) |
| 3308 | if (allocated(env_alloc)) deallocate(env_alloc) | 3308 | if (allocated(env_alloc)) deallocate(env_alloc) |
| 3309 | | 3309 | |
@@ -3532,12 +3532,13 @@ contains |
| 3532 | ! Trailing / on directory forces ls to list contents (not the symlink itself on macOS) | 3532 | ! Trailing / on directory forces ls to list contents (not the symlink itself on macOS) |
| 3533 | ! When pattern is non-empty, filter with grep to avoid buffer overflow on large directories | 3533 | ! When pattern is non-empty, filter with grep to avoid buffer overflow on large directories |
| 3534 | ! Use tr to convert newlines to spaces for easier parsing | 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 | if (pattern_len > 0) then | 3536 | if (pattern_len > 0) then |
| 3537 | ls_command = 'ls -1aF "' // trim(expanded_dir) // '/" 2>/dev/null | grep -i "^' // & | 3537 | ls_command = 'ls -1aF "' // trim(expanded_dir) // '/" 2>/dev/null | grep -i "^' // & |
| 3538 | - trim(pattern) // '"' | 3538 | + trim(pattern) // '" | tr ' // "'" // char(92) // 'n' // "' ' '" |
| 3539 | else | 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 | end if | 3542 | end if |
| 3542 | | 3543 | |
| 3543 | ! Debug output | 3544 | ! Debug output |
@@ -3670,9 +3671,8 @@ contains |
| 3670 | num_entries = 0 | 3671 | num_entries = 0 |
| 3671 | pos = 1 | 3672 | pos = 1 |
| 3672 | do while (pos <= output_len) | 3673 | do while (pos <= output_len) |
| 3673 | - ! Skip newline/whitespace delimiters | 3674 | + ! Skip whitespace |
| 3674 | - do while (pos <= output_len .and. (output(pos:pos) == char(10) .or. & | 3675 | + do while (pos <= output_len .and. (output(pos:pos) == ' ' .or. output(pos:pos) == char(9))) |
| 3675 | - output(pos:pos) == char(13) .or. output(pos:pos) == char(9))) | | |
| 3676 | pos = pos + 1 | 3676 | pos = pos + 1 |
| 3677 | end do | 3677 | end do |
| 3678 | | 3678 | |
@@ -3680,9 +3680,8 @@ contains |
| 3680 | | 3680 | |
| 3681 | start = pos | 3681 | start = pos |
| 3682 | | 3682 | |
| 3683 | - ! Find end of entry (newline delimiter preserves spaces in filenames) | 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) /= char(10) .and. & | 3684 | + do while (pos <= output_len .and. output(pos:pos) /= ' ') |
| 3685 | - output(pos:pos) /= char(13) .and. output(pos:pos) /= char(9)) | | |
| 3686 | pos = pos + 1 | 3685 | pos = pos + 1 |
| 3687 | end do | 3686 | end do |
| 3688 | | 3687 | |
@@ -3701,9 +3700,8 @@ contains |
| 3701 | count_pass = 0 | 3700 | count_pass = 0 |
| 3702 | pos = 1 | 3701 | pos = 1 |
| 3703 | do while (pos <= output_len .and. count_pass < num_entries) | 3702 | do while (pos <= output_len .and. count_pass < num_entries) |
| 3704 | - ! Skip newline/whitespace delimiters | 3703 | + ! Skip whitespace |
| 3705 | - do while (pos <= output_len .and. (output(pos:pos) == char(10) .or. & | 3704 | + do while (pos <= output_len .and. (output(pos:pos) == ' ' .or. output(pos:pos) == char(9))) |
| 3706 | - output(pos:pos) == char(13) .or. output(pos:pos) == char(9))) | | |
| 3707 | pos = pos + 1 | 3705 | pos = pos + 1 |
| 3708 | end do | 3706 | end do |
| 3709 | | 3707 | |
@@ -3711,9 +3709,8 @@ contains |
| 3711 | | 3709 | |
| 3712 | start = pos | 3710 | start = pos |
| 3713 | | 3711 | |
| 3714 | - ! Find end of entry (newline delimiter) | 3712 | + ! Find end of entry |
| 3715 | - do while (pos <= output_len .and. output(pos:pos) /= char(10) .and. & | 3713 | + do while (pos <= output_len .and. output(pos:pos) /= ' ') |
| 3716 | - output(pos:pos) /= char(13) .and. output(pos:pos) /= char(9)) | | |
| 3717 | pos = pos + 1 | 3714 | pos = pos + 1 |
| 3718 | end do | 3715 | end do |
| 3719 | | 3716 | |
@@ -3894,12 +3891,34 @@ contains |
| 3894 | ! No completions found | 3891 | ! No completions found |
| 3895 | return | 3892 | return |
| 3896 | else if (num_completions == 1) then | 3893 | else if (num_completions == 1) then |
| 3897 | - ! Single completion - add prefix back (preserve spacing) | 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 |
| 3898 | if (last_space_pos > 0) 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 |
| 3899 | completed_line = prefix_part(:last_space_pos) // trim(completions(1)) | 3917 | completed_line = prefix_part(:last_space_pos) // trim(completions(1)) |
| 3900 | else | 3918 | else |
| 3901 | completed_line = trim(completions(1)) | 3919 | completed_line = trim(completions(1)) |
| 3902 | end if | 3920 | end if |
| | 3921 | + end block |
| 3903 | completed = .true. | 3922 | completed = .true. |
| 3904 | else | 3923 | else |
| 3905 | ! Multiple completions | 3924 | ! Multiple completions |