JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.79k stars 5.49k forks source link

incorrect filename tab completion for names containing = #22450

Open stevengj opened 7 years ago

stevengj commented 7 years ago

I noticed this when looking at JuliaLang/IJulia.jl#557 (cc @kalmarek). In the REPL:

touch("22222")
"r=2<tab>

completes to "r=22222" even though there is no such filename. Specifically, Base.REPLCompletions.completions("\"r=2", 4) returns (String["22222\""], 4:4, true) if the file "22222" is the only file beginning with "2" in the current directory.

Seems to occur in Julia 0.4–0.7. cc @Keno

dhoegh commented 7 years ago

The issue is that REPLCompletion is looking from the end of the string until it reaches a character that cannot be in a filename. This allows for things like:

julia> touch("test.txt")
julia> "testfile = te<tab>"

The exact code for this behavior is REPLCompletion#L459.

stevengj commented 7 years ago

I'm still confused. Why does it think = is a "character that cannot be in a filename"?

dhoegh commented 7 years ago

The regex seems to be to restrictive. Maybe it should be changed to:

m = match(r"[:*?<>|]| (?!\\)", reverse(partial))
stevengj commented 7 years ago

Why not just look for the beginning of the string?

dhoegh commented 7 years ago

Because the then it will also look to the start of a cmd string. This would remove ability to `ls C:\\User<tab>

stevengj commented 7 years ago

You could look for the beginning ". If you find a beginning ` instead, you could backtrack and look for whitespace (since unquoted Cmd arguments are whitespace-separated.)

dhoegh commented 7 years ago

@stevengj something like commit 0047250?

stevengj commented 7 years ago

It still is looking for characters like ! ... I'm thinking more along the lines of:

# Search backwards in s for the beginning " of a string.  
# If incmd is true, then alternatively looks for a beginning `,
# and if so looks for whitespace.  Returns the starting position
# (the first character after the opening ")
function findstartstring(s, incmd)
    i = endof(s)
    i = incmd ? rsearch(s, ('"','`'), i) : rsearch(s, '"', i)
    if incmd && i > start(s) && s[i] == '`'
        i = findlast(isspace, s)
    end
    return nextind(s, i)
end
dhoegh commented 7 years ago

The regex do not look for !. The (?!\\) part of the regex is a negative look ahead. That ensures it do not match an escaped character like \" . See the explanation from regex101. image

This it quite important to find the start of the string and not just a " in the string. The input to the function could look like: "s=\"test\\\" which is equivalent to writing s="test\" in the console. To get whole string we need to consider whether " is escaped.

stevengj commented 7 years ago

Ah, I see. In the Cmd case shouldn't it be r"[\"`\s](?!\\)" then?

dhoegh commented 7 years ago

My first thought was the same, but as quotes has special meaning inside backticks it is unfortunately not as simple. Because a space inside quotes is still a valid path. I have implemented a quote aware completion in backticks in https://github.com/dhoegh/julia/commit/3639605272a99b1d4e473f0f39d89d91ba3d1d84, if you agree with the approach I will submit it as a PR.

mlhetland commented 4 years ago

Just came across a similar situation (perhaps the same issue). If I have two files called bar10 and bar20, tab completion first completes bar. If I press tab again, I get both names listed; if I press 1 and press tab again, it completes bar10.

If, however, the files are called foo bar10 and foo bar20, this doesn't work. It completes foo bar, but then tab doesn't do anything, and pressing 1 doesn't help.

Interestingly, if I have the files bar10 and bar20 there, still, the tab completion thinks that the last half of the completed name (i.e., bar) is the beginning of one of them, producing the same behavior as initially.