chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using OpenTap.Translation;
 
namespace OpenTap.Package
{
    internal class AutoCorrectException : Exception
    {
        public AutoCorrectException(string message) : base(message)
        {
 
        }
    }
    internal static class AutoCorrectPackageNames
    {
        /// <summary>
        /// Correct each name in the string array based on the available packages in the repositories.
        /// If a package name does not exist, an exception is thrown.
        /// </summary>
        /// <param name="names"></param>
        /// <param name="repositories"></param>
        /// <returns></returns>
        public static string[] Correct(string[] names, IEnumerable<IPackageRepository> repositories)
        {
            if (names == null || names.Length == 0) return names;
            // Copy the input array to use as return value
            var result = names.ToArray();
 
            var repos = repositories.ToArray();
            List<string> onlinePackages = null;
 
            var packageCache = PackageRepositoryHelpers.DetermineRepositoryType(new Uri(PackageCacheHelper.PackageCacheDirectory).AbsoluteUri);
            var knownPackages = Installation.Current.GetPackages().Select(p => p.Name)
                .Concat(packageCache.GetPackageNames()).ToHashSet();
 
 
            for (int i = 0; i < names.Length; i++)
            {
                var name = names[i];
                if (File.Exists(name)) continue;
 
                var notFoundMessage = $"Package '{name}' not found.";
 
                if (string.IsNullOrWhiteSpace(name) || knownPackages.Contains(name))
                {
                    result[i] = name;
                    continue;
                }
                // Checking the repositories is slow. Postpone it as much as possible.
                // In most cases we can resolve a correctly spelled package from local caches
 
                if (onlinePackages == null)
                {
                    onlinePackages = repos.SelectMany(r => r.GetPackageNames()).ToList();
                    foreach (var pkg in onlinePackages)
                    {
                        knownPackages.Add(pkg);
                    }
 
                    if (knownPackages.Contains(name))
                    {
                        result[i] = name;
                        continue;
                    }
                }
 
                var matchThreshold = 3;
                var matcher = new FuzzyMatcher(name, matchThreshold);
                var matchList = knownPackages.Select(matcher.Score).Where(m => m.Score <= matchThreshold).ToArray();
 
                // If any match is almost a perfect match, only consider perfect matches
                if (matchList.Any(m => m.Score == 0))
                    matchList = matchList.Where(m => m.Score == 0).ToArray();
 
                var scores = matchList.OrderBy(m => m.Score).Take(5).ToArray();
 
                if (scores.Length == 0)
                {
                    // There are no packages 
                    throw new AutoCorrectException(notFoundMessage);
                }
 
                var options = scores.Select(s => s.Candidate).ToList();
 
                var req = new AutoCorrectRequest(name, options);
                UserInput.Request(req);
                if (req.Choice == req.NegativeAnswer)
                    throw new AutoCorrectException(notFoundMessage);
                if (req.Choice == req.Yes)
                    result[i] = options[0];
                else result[i] = req.Choice;
            }
 
            return result;
        }
    }
 
    [Display("Correct package name?")]
    class AutoCorrectRequest : IStringLocalizer
    {
        public string NegativeAnswer => Options.Length == 1 ? No : Cancel;
        public string Yes => this.Translate("Yes");
        public string No => this.Translate("No");
        public string Cancel => this.Translate("Cancel");
 
        public string SingleOptionMessage => this.TranslateFormat("Package '{0}' not found. Did you mean '{1}'?").Format(Package, Options.FirstOrDefault());
        public string MultipleOptionsMessage => this.TranslateFormat("Package '{0}' not found. Did you mean:").Format(Package);
 
        [Layout(LayoutMode.FullRow)]
        [Browsable(true)]
        public string Message => Options.Length == 1 ? SingleOptionMessage : MultipleOptionsMessage;
 
        // If there is only one option, provide a yes/no dialog
        // Otherwise, provide a ranked list
        public string[] Choices => Options.Length == 1 ? [Yes, No] : [.. Options, Cancel];
        [Submit]
        [Layout(LayoutMode.FullRow | LayoutMode.FloatBottom)]
        [AvailableValues(nameof(Choices))]
        public string Choice { get; set; }
 
        private readonly string Package;
        private readonly string[] Options = [];
        public AutoCorrectRequest(string package, IEnumerable<string> options)
        {
            Package = package;
            Options = [.. options];
            Choice = Choices[0];
        }
 
        public AutoCorrectRequest()
        {
            // default constructor needed so the type can be instantiated without parameters
        }
    }
}