@@ -11,7 +11,7 @@ function _shared_envs()
1111 return possible
1212end
1313
14- function complete_activate (options, partial, i1, i2; hint:: Bool )
14+ function complete_activate (options, partial, i1, i2; hint:: Bool , arguments = [] )
1515 shared = get (options, :shared , false )
1616 if shared
1717 return _shared_envs ()
5454
5555
5656const JULIA_UUID = UUID (" 1222c4b2-2114-5bfd-aeef-88e4692bbb3e" )
57+
58+ # Helper function to extract already-specified package names from arguments
59+ # Used for deduplicating completion suggestions (issue #4098)
60+ function extract_specified_names (arguments)
61+ specified_names = Set {String} ()
62+ # Exclude the last argument, which is the one currently being completed
63+ for i in 1 : (length (arguments) - 1 )
64+ arg = arguments[i]
65+ arg_str = arg isa String ? arg : arg. raw
66+ # Extract package name (before any @, #, =, or : specifiers)
67+ pkg_name = first (split (arg_str, [' @' , ' #' , ' =' , ' :' ]))
68+ push! (specified_names, pkg_name)
69+ end
70+ return specified_names
71+ end
5772function complete_remote_package! (comps, partial; hint:: Bool )
5873 isempty (partial) && return true # true means returned early
5974 found_match = ! isempty (comps)
@@ -95,38 +110,46 @@ function complete_remote_package!(comps, partial; hint::Bool)
95110 return false # false means performed full search
96111end
97112
98- function complete_help (options, partial; hint:: Bool )
113+ function complete_help (options, partial; hint:: Bool , arguments = [] )
99114 names = String[]
100115 for cmds in values (SPECS)
101116 append! (names, [spec. canonical_name for spec in values (cmds)])
102117 end
103118 return sort! (unique! (append! (names, collect (keys (SPECS)))))
104119end
105120
106- function complete_installed_packages (options, partial; hint:: Bool )
121+ function complete_installed_packages (options, partial; hint:: Bool , arguments = [] )
107122 env = try
108123 EnvCache ()
109124 catch err
110125 err isa PkgError || rethrow ()
111126 return String[]
112127 end
113128 mode = get (options, :mode , PKGMODE_PROJECT)
114- return mode == PKGMODE_PROJECT ?
129+ packages = mode == PKGMODE_PROJECT ?
115130 collect (keys (env. project. deps)) :
116131 unique! ([entry. name for (uuid, entry) in env. manifest])
132+
133+ # Filter out already-specified packages
134+ specified_names = extract_specified_names (arguments)
135+ return filter (pkg -> ! (pkg in specified_names), packages)
117136end
118137
119- function complete_all_installed_packages (options, partial; hint:: Bool )
138+ function complete_all_installed_packages (options, partial; hint:: Bool , arguments = [] )
120139 env = try
121140 EnvCache ()
122141 catch err
123142 err isa PkgError || rethrow ()
124143 return String[]
125144 end
126- return unique! ([entry. name for (uuid, entry) in env. manifest])
145+ packages = unique! ([entry. name for (uuid, entry) in env. manifest])
146+
147+ # Filter out already-specified packages
148+ specified_names = extract_specified_names (arguments)
149+ return filter (pkg -> ! (pkg in specified_names), packages)
127150end
128151
129- function complete_installed_packages_and_compat (options, partial; hint:: Bool )
152+ function complete_installed_packages_and_compat (options, partial; hint:: Bool , arguments = [] )
130153 env = try
131154 EnvCache ()
132155 catch err
@@ -139,17 +162,21 @@ function complete_installed_packages_and_compat(options, partial; hint::Bool)
139162 end
140163end
141164
142- function complete_fixed_packages (options, partial; hint:: Bool )
165+ function complete_fixed_packages (options, partial; hint:: Bool , arguments = [] )
143166 env = try
144167 EnvCache ()
145168 catch err
146169 err isa PkgError || rethrow ()
147170 return String[]
148171 end
149- return unique! ([entry. name for (uuid, entry) in env. manifest. deps if Operations. isfixed (entry)])
172+ packages = unique! ([entry. name for (uuid, entry) in env. manifest. deps if Operations. isfixed (entry)])
173+
174+ # Filter out already-specified packages
175+ specified_names = extract_specified_names (arguments)
176+ return filter (pkg -> ! (pkg in specified_names), packages)
150177end
151178
152- function complete_add_dev (options, partial, i1, i2; hint:: Bool )
179+ function complete_add_dev (options, partial, i1, i2; hint:: Bool , arguments = [] )
153180 comps, idx, _ = complete_local_dir (partial, i1, i2)
154181 if occursin (Base. Filesystem. path_separator_re, partial)
155182 return comps, idx, ! isempty (comps)
@@ -159,12 +186,17 @@ function complete_add_dev(options, partial, i1, i2; hint::Bool)
159186 if ! returned_early
160187 append! (comps, filter! (startswith (partial), [info. name for info in values (Types. stdlib_infos ())]))
161188 end
189+
190+ # Filter out already-specified packages
191+ specified_names = extract_specified_names (arguments)
192+ filter! (pkg -> ! (pkg in specified_names), comps)
193+
162194 return comps, idx, ! isempty (comps)
163195end
164196
165197# TODO : Move
166198import Pkg: Operations, Types, Apps
167- function complete_installed_apps (options, partial; hint)
199+ function complete_installed_apps (options, partial; hint, arguments = [] )
168200 manifest = try
169201 Types. read_manifest (joinpath (Apps. app_env_folder (), " AppManifest.toml" ))
170202 catch err
@@ -176,7 +208,11 @@ function complete_installed_apps(options, partial; hint)
176208 append! (apps, keys (entry. apps))
177209 push! (apps, entry. name)
178210 end
179- return unique! (apps)
211+ apps = unique! (apps)
212+
213+ # Filter out already-specified packages
214+ specified_names = extract_specified_names (arguments, partial)
215+ return filter (app -> ! (app in specified_names), apps)
180216end
181217
182218# #######################
@@ -215,7 +251,7 @@ complete_opt(opt_specs) =
215251)
216252
217253function complete_argument (
218- spec:: CommandSpec , options:: Vector{String} ,
254+ spec:: CommandSpec , options:: Vector{String} , arguments :: Vector ,
219255 partial:: AbstractString , offset:: Int ,
220256 index:: Int ; hint:: Bool
221257 )
@@ -228,10 +264,15 @@ function complete_argument(
228264 @error " REPLMode indicates a completion function called :$(spec. completions) that cannot be found in REPLExt"
229265 rethrow ()
230266 end
231- spec. completions = function (opts, partial, offset, index; hint:: Bool )
232- return applicable (completions, opts, partial, offset, index) ?
233- completions (opts, partial, offset, index; hint) :
234- completions (opts, partial; hint)
267+ spec. completions = function (opts, partial, offset, index; hint:: Bool , arguments = [])
268+ # Wrapper that normalizes completion function calls.
269+ if applicable (completions, opts, partial, offset, index)
270+ # Function takes 4 positional args: (opts, partial, offset, index; hint, arguments)
271+ return completions (opts, partial, offset, index; hint, arguments)
272+ else
273+ # Function takes 2 positional args: (opts, partial; hint, arguments)
274+ return completions (opts, partial; hint, arguments)
275+ end
235276 end
236277 end
237278 spec. completions === nothing && return String[]
@@ -243,7 +284,7 @@ function complete_argument(
243284 e isa PkgError && return String[]
244285 rethrow ()
245286 end
246- return spec. completions (opts, partial, offset, index; hint)
287+ return spec. completions (opts, partial, offset, index; hint, arguments )
247288end
248289
249290function _completions (input, final, offset, index; hint:: Bool )
@@ -269,11 +310,11 @@ function _completions(input, final, offset, index; hint::Bool)
269310 command_is_focused () && return String[], 0 : - 1 , false
270311
271312 if final # complete arg by default
272- x = complete_argument (statement. spec, statement. options, partial, offset, index; hint)
313+ x = complete_argument (statement. spec, statement. options, statement . arguments, partial, offset, index; hint)
273314 else # complete arg or opt depending on last token
274315 x = is_opt (partial) ?
275316 complete_opt (statement. spec. option_specs) :
276- complete_argument (statement. spec, statement. options, partial, offset, index; hint)
317+ complete_argument (statement. spec, statement. options, statement . arguments, partial, offset, index; hint)
277318 end
278319 end
279320
0 commit comments