Exemplo n.º 1
0
        /// <summary>
        /// This is overridden to initialize the package
        /// </summary>
        /// <remarks>This method is called right after the package is sited, so this is the place where you can
        /// put all the initialization code that relies on services provided by Visual Studio.</remarks>
        protected override async System.Threading.Tasks.Task InitializeAsync(
            System.Threading.CancellationToken cancellationToken, IProgress <ServiceProgressData> progress)
        {
            Trace.WriteLine($"Entering Initialize() of {this.ToString()}");

            await base.InitializeAsync(cancellationToken, progress);

            // When initialized asynchronously, we *may* be on a background thread at this point.  Do any
            // initialization that requires the UI thread after switching to the UI thread.
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            Instance = this;

            try
            {
                var configuration = new SpellCheckerConfiguration();
                configuration.Load(SpellingConfigurationFile.GlobalConfigurationFilename);

                if (configuration.EnableWpfTextBoxSpellChecking)
                {
                    this.ConnectSpellChecker();
                }
            }
            catch (Exception ex)
            {
                // Ignore any exceptions
                Debug.WriteLine(ex);
            }
        }
Exemplo n.º 2
0
        //=====================================================================

        /// <summary>
        /// Get the configuration settings for the specified buffer
        /// </summary>
        /// <param name="buffer">The buffer for which to get the configuration settings</param>
        /// <returns>The spell checker configuration settings for the buffer or null if one is not provided or
        /// is disabled for the given buffer.</returns>
        public SpellCheckerConfiguration GetConfiguration(ITextBuffer buffer)
        {
            SpellCheckerConfiguration config = null;
            bool isDisabled = false;

            // If not given a buffer or already checked for and found to be disabled, don't go any further
            if (buffer != null && !buffer.Properties.TryGetProperty(SpellCheckerDisabledKey, out isDisabled) &&
                !buffer.Properties.TryGetProperty(typeof(SpellCheckerConfiguration), out config))
            {
                // Generate the configuration settings unique to the file
                config = this.GenerateConfiguration(buffer);

                if (!config.SpellCheckAsYouType || config.IsExcludedByExtension(buffer.GetFilenameExtension()))
                {
                    // Mark it as disabled so that we don't have to check again
                    buffer.Properties[SpellCheckerDisabledKey] = true;
                    config = null;
                }
                else
                {
                    buffer.Properties[typeof(SpellCheckerConfiguration)] = config;
                }
            }

            return(config);
        }
        //=====================================================================

        /// <summary>
        /// Get the configuration settings for the specified buffer
        /// </summary>
        /// <param name="buffer">The buffer for which to get the configuration settings</param>
        /// <returns>The spell checker configuration settings for the buffer or null if one is not provided or
        /// is disabled for the given buffer.</returns>
        public SpellCheckerConfiguration GetConfiguration(ITextBuffer buffer)
        {
            SpellCheckerConfiguration config = null;

            // If not given a buffer or already checked for and found to be disabled, don't go any further
            if (buffer != null && !buffer.Properties.TryGetProperty(SpellCheckerDisabledKey, out bool isDisabled) &&
                !buffer.Properties.TryGetProperty(typeof(SpellCheckerConfiguration), out config))
            {
#pragma warning disable VSTHRD010
                // Generate the configuration settings unique to the file
                config = this.GenerateConfiguration(buffer);
#pragma warning restore VSTHRD010

                if (config == null || !config.SpellCheckAsYouType || config.ShouldExcludeFile(buffer.GetFilename()))
                {
                    // Mark it as disabled so that we don't have to check again
                    buffer.Properties[SpellCheckerDisabledKey] = true;
                    config = null;
                }
                else
                {
                    buffer.Properties[typeof(SpellCheckerConfiguration)] = config;
                }
            }

            return(config);
        }
Exemplo n.º 4
0
        //=====================================================================

        /// <summary>
        /// Creates a tag provider for the specified view and buffer
        /// </summary>
        /// <typeparam name="T">The tag type</typeparam>
        /// <param name="textView">The text view</param>
        /// <param name="buffer">The text buffer</param>
        /// <returns>The tag provider for the specified view and buffer or null if the buffer does not match the
        /// one in the view or spell checking as you type is disabled.</returns>
        public ITagger <T> CreateTagger <T>(ITextView textView, ITextBuffer buffer) where T : ITag
        {
            SpellingTagger spellingTagger;

            // Make sure we only tagging top buffer and only if wanted
            if (textView.TextBuffer != buffer || !SpellCheckerConfiguration.SpellCheckAsYouType ||
                SpellCheckerConfiguration.IsExcludedByExtension(buffer.GetFilenameExtension()))
            {
                return(null);
            }

            if (textView.Properties.TryGetProperty(typeof(SpellingTagger), out spellingTagger))
            {
                return(spellingTagger as ITagger <T>);
            }

            var dictionary = spellingDictionaryFactory.GetDictionary(buffer);

            if (dictionary == null)
            {
                return(null);
            }

            var naturalTextAggregator = aggregatorFactory.CreateTagAggregator <INaturalTextTag>(textView,
                                                                                                TagAggregatorOptions.MapByContentType);
            var urlAggregator = aggregatorFactory.CreateTagAggregator <IUrlTag>(textView);

            spellingTagger = new SpellingTagger(buffer, textView, naturalTextAggregator, urlAggregator, dictionary);
            textView.Properties[typeof(SpellingTagger)] = spellingTagger;

            return(spellingTagger as ITagger <T>);
        }
        /// <summary>
        /// This is overridden to initialize the package
        /// </summary>
        /// <remarks>This method is called right after the package is sited, so this is the place where you can
        /// put all the initialization code that relies on services provided by Visual Studio.</remarks>
        protected override void Initialize()
        {
            Trace.WriteLine(String.Format(CultureInfo.CurrentCulture, "Entering Initialize() of {0}",
                                          this.ToString()));

            base.Initialize();

            Instance = this;

            try
            {
                var configuration = new SpellCheckerConfiguration();
                configuration.Load(SpellingConfigurationFile.GlobalConfigurationFilename);

                if (configuration.EnableWpfTextBoxSpellChecking)
                {
                    this.ConnectSpellChecker();
                }
            }
            catch (Exception ex)
            {
                // Ignore any exceptions
                Debug.WriteLine(ex);
            }
        }
Exemplo n.º 6
0
        //=====================================================================

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="buffer">The text buffer</param>
        /// <param name="view">The text view</param>
        /// <param name="naturalTextAggregator">The tag aggregator</param>
        /// <param name="urlAggregator">The URL aggregator</param>
        /// <param name="configuration">The spell checker configuration to use</param>
        /// <param name="dictionary">The spelling dictionary to use</param>
        public SpellingTagger(ITextBuffer buffer, ITextView view,
                              ITagAggregator <INaturalTextTag> naturalTextAggregator, ITagAggregator <IUrlTag> urlAggregator,
                              SpellCheckerConfiguration configuration, SpellingDictionary dictionary)
        {
            _isClosed = false;
            _buffer   = buffer;
            _naturalTextAggregator = naturalTextAggregator;
            _urlAggregator         = urlAggregator;
            _dispatcher            = Dispatcher.CurrentDispatcher;
            this.configuration     = configuration;
            _dictionary            = dictionary;

            _dirtySpans      = new List <SnapshotSpan>();
            _misspellings    = new List <MisspellingTag>();
            wordsIgnoredOnce = new List <IgnoredWord>();

            _buffer.Changed += BufferChanged;
            _naturalTextAggregator.TagsChanged += AggregatorTagsChanged;
            _urlAggregator.TagsChanged         += AggregatorTagsChanged;
            _dictionary.DictionaryUpdated      += DictionaryUpdated;
            _dictionary.ReplaceAll             += ReplaceAll;
            _dictionary.IgnoreOnce             += IgnoreOnce;

            view.Closed += ViewClosed;

            // To start with, the entire buffer is dirty.  Split this into chunks so we update pieces at a time.
            ITextSnapshot snapshot = _buffer.CurrentSnapshot;

            foreach (var line in snapshot.Lines)
            {
                AddDirtySpan(line.Extent);
            }
        }
        /// <inheritdoc />
        public bool ShouldIgnoreWord(string word)
        {
            lock (ignoredWords)
            {
                if (ignoredWords.Contains(word))
                {
                    return(true);
                }
            }

            return(SpellCheckerConfiguration.ShouldIgnoreWord(word));
        }
Exemplo n.º 8
0
        //=====================================================================

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="buffer">The text buffer</param>
        /// <param name="view">The text view</param>
        /// <param name="naturalTextAggregator">The tag aggregator</param>
        /// <param name="urlAggregator">The URL aggregator</param>
        /// <param name="configuration">The spell checker configuration to use</param>
        /// <param name="dictionary">The spelling dictionary to use</param>
        public SpellingTagger(ITextBuffer buffer, ITextView view,
                              ITagAggregator <INaturalTextTag> naturalTextAggregator, ITagAggregator <IUrlTag> urlAggregator,
                              SpellCheckerConfiguration configuration, SpellingDictionary dictionary)
        {
            _isClosed = false;
            _buffer   = buffer;
            _naturalTextAggregator = naturalTextAggregator;
            _urlAggregator         = urlAggregator;
            _dispatcher            = Dispatcher.CurrentDispatcher;
            this.configuration     = configuration;
            _dictionary            = dictionary;

            _dirtySpans        = new List <SnapshotSpan>();
            _misspellings      = new List <MisspellingTag>();
            wordsIgnoredOnce   = new List <IgnoredOnceWord>();
            inlineIgnoredWords = new List <InlineIgnoredWord>();

            string filename = buffer.GetFilename();

            wordSplitter = new WordSplitter
            {
                Configuration = configuration,
                Mnemonic      = ClassifierFactory.GetMnemonic(filename),
                IsCStyleCode  = ClassifierFactory.IsCStyleCode(filename)
            };

            _buffer.Changed += BufferChanged;
            _naturalTextAggregator.TagsChanged += AggregatorTagsChanged;
            _urlAggregator.TagsChanged         += AggregatorTagsChanged;
            _dictionary.DictionaryUpdated      += DictionaryUpdated;
            _dictionary.ReplaceAll             += ReplaceAll;
            _dictionary.IgnoreOnce             += IgnoreOnce;

            view.Closed += ViewClosed;

            // Strings in SQL script can contain escaped single quotes which are apostrophes.  Unescape them
            // so that they are spell checked correctly.
            unescapeApostrophes = buffer.ContentType.IsOfType("SQL Server Tools");

            // To start with, the entire buffer is dirty.  Split this into chunks so we update pieces at a time.
            ITextSnapshot snapshot = _buffer.CurrentSnapshot;

            foreach (var line in snapshot.Lines)
            {
                AddDirtySpan(line.Extent);
            }
        }
Exemplo n.º 9
0
        //=====================================================================

        /// <summary>
        /// Generate the configuration to use when spell checking the given text buffer
        /// </summary>
        /// <param name="buffer">The text buffer for which to generate a configuration</param>
        /// <returns>The generated configuration to use</returns>
        /// <remarks>The configuration is a merger of the global settings plus any solution, project, folder, and
        /// file settings related to the text buffer.</remarks>
        private SpellCheckerConfiguration GenerateConfiguration(ITextBuffer buffer)
        {
            ProjectItem projectItem, fileItem;
            string      bufferFilename, filename, projectPath, projectFilename = null;

            // Start with the global configuration
            var config = new SpellCheckerConfiguration();

            try
            {
                config.Load(SpellingConfigurationFile.GlobalConfigurationFilename);

                var dte2 = (globalServiceProvider == null) ? null :
                           globalServiceProvider.GetService(typeof(SDTE)) as DTE2;

                if (dte2 != null && dte2.Solution != null && !String.IsNullOrWhiteSpace(dte2.Solution.FullName))
                {
                    var solution = dte2.Solution;

                    // Clear the global dictionary cache when a change in solution is detected.  This handles
                    // cases where only the MEF components are loaded and not the package (i.e. a configuration
                    // has not been edited).  See VSSpellCheckerPackage.solutionEvents_AfterClosing().
                    if (lastSolutionName == null || !lastSolutionName.Equals(solution.FullName,
                                                                             StringComparison.OrdinalIgnoreCase))
                    {
                        GlobalDictionary.ClearDictionaryCache();
                        lastSolutionName = solution.FullName;
                    }

                    // See if there is a solution configuration
                    filename    = solution.FullName + ".vsspell";
                    projectItem = solution.FindProjectItem(filename);

                    if (projectItem != null)
                    {
                        config.Load(filename);
                    }

                    // Find the project item for the file we are opening
                    bufferFilename = buffer.GetFilename();
                    projectItem    = (bufferFilename != null) ? solution.FindProjectItem(bufferFilename) : null;

                    if (projectItem != null)
                    {
                        fileItem = projectItem;

                        // If we have a project (we should), see if it has settings
                        if (projectItem.ContainingProject != null &&
                            !String.IsNullOrWhiteSpace(projectItem.ContainingProject.FullName))
                        {
                            projectFilename = projectItem.ContainingProject.FullName;
                            filename        = projectFilename + ".vsspell";
                            projectItem     = solution.FindProjectItem(filename);

                            if (projectItem != null)
                            {
                                config.Load(filename);
                            }

                            // Get the full path based on the project.  The buffer filename will refer to the actual
                            // path which may be to a linked file outside the project's folder structure.
                            projectPath = Path.GetDirectoryName(filename);
                            filename    = Path.GetDirectoryName((string)fileItem.Properties.Item("FullPath").Value);

                            // Search for folder-specific configuration files
                            if (filename.StartsWith(projectPath, StringComparison.OrdinalIgnoreCase))
                            {
                                // Then check subfolders.  No need to check the root folder as the project
                                // settings cover it.
                                if (filename.Length > projectPath.Length)
                                {
                                    foreach (string folder in filename.Substring(projectPath.Length + 1).Split('\\'))
                                    {
                                        projectPath = Path.Combine(projectPath, folder);
                                        filename    = Path.Combine(projectPath, folder + ".vsspell");
                                        projectItem = solution.FindProjectItem(filename);

                                        if (projectItem != null)
                                        {
                                            config.Load(filename);
                                        }
                                    }
                                }
                            }

                            // If the item looks like a dependent file item, look for a settings file related to
                            // the parent file item.
                            if (fileItem.Collection != null && fileItem.Collection.Parent != null)
                            {
                                projectItem = fileItem.Collection.Parent as ProjectItem;

                                if (projectItem != null && projectItem.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFile)
                                {
                                    filename    = (string)projectItem.Properties.Item("FullPath").Value + ".vsspell";
                                    projectItem = solution.FindProjectItem(filename);

                                    if (projectItem != null)
                                    {
                                        config.Load(filename);
                                    }
                                }
                            }

                            // And finally, look for file-specific settings for the item itself
                            filename    = (string)fileItem.Properties.Item("FullPath").Value + ".vsspell";
                            projectItem = solution.FindProjectItem(filename);

                            if (projectItem != null)
                            {
                                config.Load(filename);
                            }
                        }
                        else
                        if (projectItem.Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems)
                        {
                            // Looks like a solution item, see if a related setting file exists
                            filename = bufferFilename + ".vsspell";

                            projectItem = solution.FindProjectItem(filename);

                            if (projectItem != null)
                            {
                                config.Load(filename);
                            }
                        }
                    }

                    // Load code analysis dictionaries if wanted
                    if (projectFilename != null && config.CadOptions.ImportCodeAnalysisDictionaries)
                    {
                        // I'm not sure if there's a better way to do this but it does seem to work.  We need to
                        // find one or more arbitrary files with an item type of "CodeAnalysisDictionary".  We
                        // do so by getting the MSBuild project from the global project collection and using its
                        // GetItems() method to find them.
                        var loadedProject = Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.GetLoadedProjects(
                            projectFilename).FirstOrDefault();

                        if (loadedProject != null)
                        {
                            // Typically there is only one but multiple files are supported
                            foreach (var cad in loadedProject.GetItems("CodeAnalysisDictionary"))
                            {
                                filename = Path.Combine(Path.GetDirectoryName(projectFilename), cad.EvaluatedInclude);

                                if (File.Exists(filename))
                                {
                                    config.ImportCodeAnalysisDictionary(filename);
                                }
                            }
                        }
                    }

                    if (bufferFilename != null && config.DetermineResourceFileLanguageFromName &&
                        Path.GetExtension(bufferFilename).Equals(".resx", StringComparison.OrdinalIgnoreCase))
                    {
                        // Localized resource files are expected to have filenames in the format
                        // BaseName.Language.resx (i.e. LocalizedForm.de-DE.resx).
                        bufferFilename = Path.GetExtension(Path.GetFileNameWithoutExtension(bufferFilename));

                        if (bufferFilename.Length > 1)
                        {
                            bufferFilename = bufferFilename.Substring(1);

                            SpellCheckerDictionary match;

                            if (SpellCheckerDictionary.AvailableDictionaries(
                                    config.AdditionalDictionaryFolders).TryGetValue(bufferFilename, out match))
                            {
                                // Clear any existing dictionary languages and use just the one that matches the
                                // file's language.
                                config.DictionaryLanguages.Clear();
                                config.DictionaryLanguages.Add(match.Culture);
                            }
                        }
                    }
                }
                else
                if (lastSolutionName != null)
                {
                    // A solution was closed and a file has been opened outside of a solution so clear the
                    // cache and use the global dictionaries.
                    GlobalDictionary.ClearDictionaryCache();
                    lastSolutionName = null;
                }
            }
            catch (Exception ex)
            {
                // Ignore errors, we just won't load the configurations after the point of failure
                System.Diagnostics.Debug.WriteLine(ex);
            }

            return(config);
        }
Exemplo n.º 10
0
        /// <summary>
        /// Get all words in the specified text string
        /// </summary>
        /// <param name="text">The text to break into words</param>
        /// <returns>An enumerable list of word spans</returns>
        internal static IEnumerable <Microsoft.VisualStudio.Text.Span> GetWordsInText(string text)
        {
            if (String.IsNullOrWhiteSpace(text))
            {
                yield break;
            }

            for (int i = 0, end = 0; i < text.Length; i++)
            {
                // Skip escape sequences.  If not, they can end up as part of the word or cause words to be
                // missed.  For example, "This\r\nis\ta\ttest \x22missing\x22" would incorrectly yield "nis",
                // "ta", and "ttest" and incorrectly exclude "missing".  This can cause the occasional false
                // positive in file paths (i.e. \Folder\transform\File.txt flags "ransform" as a misspelled word
                // because of the lowercase "t" following the backslash) but I can live with that.  If they are
                // common enough, they can be added to the configuration's ignored word list as an escaped word.
                if (text[i] == '\\')
                {
                    end = i + 1;

                    if (end < text.Length)
                    {
                        // Skip escaped words.  Only need to check the escape sequence letters.
                        switch (text[end])
                        {
                        case 'a':       // BEL
                        case 'b':       // BS
                        case 'f':       // FF
                        case 'n':       // LF
                        case 'r':       // CR
                        case 't':       // TAB
                        case 'v':       // VT
                        case 'x':       // Hex value
                        case 'u':       // Unicode value
                        case 'U':
                        {
                            // Find the end of the word
                            int wordEnd = end;

                            while (++wordEnd < text.Length && !IsWordBreakCharacter(text[wordEnd]))
                            {
                                ;
                            }

                            if (SpellCheckerConfiguration.ShouldIgnoreWord(
                                    text.Substring(end - 1, --wordEnd - i + 1)))
                            {
                                i = wordEnd;
                                continue;
                            }

                            break;
                        }
                        }

                        // Escape sequences
                        switch (text[end])
                        {
                        case '\'':
                        case '\"':
                        case '\\':
                        case '?':       // Anti-Trigraph
                        case '0':       // NUL or Octal
                        case 'a':       // BEL
                        case 'b':       // BS
                        case 'f':       // FF
                        case 'n':       // LF
                        case 'r':       // CR
                        case 't':       // TAB
                        case 'v':       // VT
                            i++;
                            break;

                        case 'x':       // xh[h[h[h]]] or xhh[hh]
                            while (++end < text.Length && (end - i) < 6 && (Char.IsDigit(text[end]) ||
                                                                            (Char.ToLower(text[end]) >= 'a' && Char.ToLower(text[end]) <= 'f')))
                            {
                                ;
                            }

                            i = --end;
                            break;

                        case 'u':       // uhhhh
                            while (++end < text.Length && (end - i) < 6 && (Char.IsDigit(text[end]) ||
                                                                            (Char.ToLower(text[end]) >= 'a' && Char.ToLower(text[end]) <= 'f')))
                            {
                                ;
                            }

                            if ((--end - i) == 5)
                            {
                                i = end;
                            }
                            break;

                        case 'U':       // Uhhhhhhhh
                            while (++end < text.Length && (end - i) < 10 && (Char.IsDigit(text[end]) ||
                                                                             (Char.ToLower(text[end]) >= 'a' && Char.ToLower(text[end]) <= 'f')))
                            {
                                ;
                            }

                            if ((--end - i) == 9)
                            {
                                i = end;
                            }
                            break;

                        default:
                            break;
                        }
                    }

                    continue;
                }

                // Skip XML entities
                if (text[i] == '&')
                {
                    end = i + 1;

                    if (end < text.Length && text[end] == '#')
                    {
                        // Numeric Reference &#n[n][n][n];
                        while (++end < text.Length && (end - i) < 7 && Char.IsDigit(text[end]))
                        {
                            ;
                        }

                        // Hexadecimal Reference &#xh[h][h][h];
                        if (end < text.Length && text[end] == 'x')
                        {
                            while (++end < text.Length && (end - i) < 8 && (Char.IsDigit(text[end]) ||
                                                                            (Char.ToLower(text[end]) >= 'a' && Char.ToLower(text[end]) <= 'f')))
                            {
                                ;
                            }
                        }

                        // Check for entity closer
                        if (end < text.Length && text[end] == ';')
                        {
                            i = end;
                        }
                    }

                    continue;
                }

                // Skip .NET format string specifiers if so indicated.  This ignores stuff like date formats
                // such as "{0:MM/dd/yyyy hh:nn tt}".
                if (text[i] == '{' && SpellCheckerConfiguration.IgnoreFormatSpecifiers)
                {
                    end = i + 1;

                    while (end < text.Length && Char.IsDigit(text[end]))
                    {
                        end++;
                    }

                    if (end < text.Length && text[end] == ':')
                    {
                        // Find the end accounting for escaped braces
                        while (++end < text.Length)
                        {
                            if (text[end] == '}')
                            {
                                if (end + 1 == text.Length || text[end + 1] != '}')
                                {
                                    break;
                                }

                                end++;
                            }
                        }
                    }

                    if (end < text.Length && text[end] == '}')
                    {
                        i = end;
                    }

                    continue;
                }

                // Skip C-style format string specifiers if so indicated.  These can cause spelling errors in
                // cases where there are multiple characters such as "%ld".  My C/C++ skills are very rusty but
                // this should cover it.
                if (text[i] == '%' && SpellCheckerConfiguration.IgnoreFormatSpecifiers)
                {
                    end = i + 1;

                    if (end < text.Length)
                    {
                        // Flags
                        switch (text[end])
                        {
                        // NOTE: A space is also a valid flag character but we can't tell if it's part of
                        // the format or just a percentage followed by a word without some lookahead which
                        // probably isn't worth the effort (i.e. "% i" vs "100% stuff").  As such, the space
                        // flag character is not included here.
                        case '-':
                        case '+':
                        case '#':
                        case '0':
                            end++;
                            break;

                        default:
                            break;
                        }

                        // Width and precision not accounting for validity to keep it simple
                        while (end < text.Length && (Char.IsDigit(text[end]) || text[end] == '.' || text[end] == '*'))
                        {
                            end++;
                        }

                        if (end < text.Length)
                        {
                            // Length
                            switch (text[end])
                            {
                            case 'h':
                            case 'l':
                                end++;

                                // Check for "hh" and "ll"
                                if (end < text.Length && text[end] == text[end - 1])
                                {
                                    end++;
                                }
                                break;

                            case 'j':
                            case 'z':
                            case 't':
                            case 'L':
                                end++;
                                break;

                            default:
                                break;
                            }

                            if (end < text.Length)
                            {
                                // And finally, the specifier
                                switch (text[end])
                                {
                                case 'd':
                                case 'i':
                                case 'u':
                                case 'o':
                                case 'x':
                                case 'X':
                                case 'f':
                                case 'F':
                                case 'e':
                                case 'E':
                                case 'g':
                                case 'G':
                                case 'a':
                                case 'A':
                                case 'c':
                                case 's':
                                case 'p':
                                case 'n':
                                    i = end;
                                    break;

                                default:
                                    break;
                                }
                            }
                        }
                    }

                    continue;
                }

                // Skip word separator
                if (IsWordBreakCharacter(text[i]))
                {
                    continue;
                }

                // Find the end of the word
                end = i;

                while (++end < text.Length && !IsWordBreakCharacter(text[end]))
                {
                    ;
                }

                // Skip XML entity reference &[name];
                if (end < text.Length && i > 0 && text[i - 1] == '&' && text[end] == ';')
                {
                    i = end;
                    continue;
                }

                // Skip leading apostrophes
                while (i < end && text[i] == '\'')
                {
                    i++;
                }

                // Skip trailing apostrophes, periods, and at-signs
                while (--end > i && (text[end] == '\'' || text[end] == '.' || text[end] == '@'))
                {
                    ;
                }

                end++;    // Move back to last match

                // Ignore anything less than two characters
                if (end - i > 1)
                {
                    yield return(Microsoft.VisualStudio.Text.Span.FromBounds(i, end));
                }

                i = --end;
            }
        }
        /// <summary>
        /// Generate the configuration to use when spell checking the given text buffer
        /// </summary>
        /// <param name="buffer">The text buffer for which to generate a configuration</param>
        /// <returns>The generated configuration to use</returns>
        /// <remarks>The configuration is a merger of the global settings plus any solution, project, folder, and
        /// file settings related to the text buffer.</remarks>
        private static SpellCheckerConfiguration GenerateConfiguration(ITextBuffer buffer)
        {
            Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();

            ProjectItem projectItem, fileItem;
            string      bufferFilename, filename, projectPath, projectFilename = null;

            // Start with the global configuration
            var config = new SpellCheckerConfiguration();

            try
            {
                config.Load(SpellingConfigurationFile.GlobalConfigurationFilename);

                if (Package.GetGlobalService(typeof(SDTE)) is DTE2 dte2 && dte2.Solution != null &&
                    !String.IsNullOrWhiteSpace(dte2.Solution.FullName))
                {
                    var solution = dte2.Solution;

                    // Clear the global dictionary cache when a change in solution is detected.  This handles
                    // cases where only the MEF components are loaded and not the package (i.e. a configuration
                    // has not been edited).  See VSSpellCheckerPackage.solutionEvents_AfterClosing().
                    if (LastSolutionName == null || !LastSolutionName.Equals(solution.FullName,
                                                                             StringComparison.OrdinalIgnoreCase))
                    {
                        WpfTextBox.WpfTextBoxSpellChecker.ClearCache();
                        GlobalDictionary.ClearDictionaryCache();
                        LastSolutionName = solution.FullName;
                    }

                    // See if there is a solution configuration
                    filename    = solution.FullName + ".vsspell";
                    projectItem = solution.FindProjectItemForFile(filename);

                    if (projectItem != null)
                    {
                        config.Load(filename);
                    }

                    // Find the project item for the file we are opening
                    bufferFilename = buffer.GetFilename();
                    projectItem    = solution.FindProjectItemForFile(bufferFilename);

                    if (projectItem != null)
                    {
                        fileItem = projectItem;

                        // If we have a project (we should), see if it has settings
                        if (projectItem.ContainingProject != null &&
                            !String.IsNullOrWhiteSpace(projectItem.ContainingProject.FullName))
                        {
                            projectFilename = projectItem.ContainingProject.FullName;

                            // Website projects are named after the folder
                            if (projectFilename.Length > 1 && projectFilename[projectFilename.Length - 1] == '\\')
                            {
                                filename = Path.GetFileName(projectFilename.Substring(0, projectFilename.Length - 1));
                                filename = projectFilename + filename + ".vsspell";
                            }
                            else
                            {
                                filename = projectFilename + ".vsspell";
                            }

                            projectItem = solution.FindProjectItemForFile(filename);

                            if (projectItem != null)
                            {
                                config.Load(filename);
                            }

                            // Get the full path based on the project.  The buffer filename will refer to the actual
                            // path which may be to a linked file outside the project's folder structure.
                            projectPath = Path.GetDirectoryName(filename);
                            filename    = Path.GetDirectoryName((string)fileItem.Properties.Item("FullPath").Value);

                            // Search for folder-specific configuration files
                            if (filename.StartsWith(projectPath, StringComparison.OrdinalIgnoreCase))
                            {
                                // Then check subfolders.  No need to check the root folder as the project
                                // settings cover it.
                                if (filename.Length > projectPath.Length)
                                {
                                    foreach (string folder in filename.Substring(projectPath.Length + 1).Split('\\'))
                                    {
                                        projectPath = Path.Combine(projectPath, folder);
                                        filename    = Path.Combine(projectPath, folder + ".vsspell");
                                        projectItem = solution.FindProjectItemForFile(filename);

                                        if (projectItem != null)
                                        {
                                            config.Load(filename);
                                        }
                                    }
                                }
                            }

                            // If the item looks like a dependent file item, look for a settings file related to
                            // the parent file item.
                            if (fileItem.Collection != null && fileItem.Collection.Parent != null)
                            {
                                projectItem = fileItem.Collection.Parent as ProjectItem;

                                if (projectItem != null && projectItem.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFile)
                                {
                                    filename    = (string)projectItem.Properties.Item("FullPath").Value + ".vsspell";
                                    projectItem = solution.FindProjectItemForFile(filename);

                                    if (projectItem != null)
                                    {
                                        config.Load(filename);
                                    }
                                }
                            }

                            // And finally, look for file-specific settings for the item itself
                            filename    = (string)fileItem.Properties.Item("FullPath").Value + ".vsspell";
                            projectItem = solution.FindProjectItemForFile(filename);

                            if (projectItem != null)
                            {
                                config.Load(filename);
                            }
                        }
                        else
                        if (projectItem.Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems)
                        {
                            // Looks like a solution item, see if a related setting file exists
                            filename = bufferFilename + ".vsspell";

                            projectItem = solution.FindProjectItemForFile(filename);

                            if (projectItem != null)
                            {
                                config.Load(filename);
                            }
                        }
                    }

                    // Load code analysis dictionaries if wanted
                    if (projectFilename != null && config.CadOptions.ImportCodeAnalysisDictionaries)
                    {
                        // Typically there is only one but multiple files are supported
                        foreach (var cad in SpellCheckFileInfo.ProjectCodeAnalysisDictionaries(projectFilename))
                        {
                            if (File.Exists(cad.CanonicalName))
                            {
                                config.ImportCodeAnalysisDictionary(cad.CanonicalName);
                            }
                        }
                    }

                    if (bufferFilename != null && config.DetermineResourceFileLanguageFromName &&
                        Path.GetExtension(bufferFilename).Equals(".resx", StringComparison.OrdinalIgnoreCase))
                    {
                        // Localized resource files are expected to have filenames in the format
                        // BaseName.Language.resx (i.e. LocalizedForm.de-DE.resx).
                        bufferFilename = Path.GetExtension(Path.GetFileNameWithoutExtension(bufferFilename));

                        if (bufferFilename.Length > 1)
                        {
                            bufferFilename = bufferFilename.Substring(1);

                            if (SpellCheckerDictionary.AvailableDictionaries(
                                    config.AdditionalDictionaryFolders).TryGetValue(bufferFilename,
                                                                                    out SpellCheckerDictionary match))
                            {
                                // Clear any existing dictionary languages and use just the one that matches the
                                // file's language.
                                config.DictionaryLanguages.Clear();
                                config.DictionaryLanguages.Add(match.Culture);
                            }
                        }
                    }
                }
                else
                if (LastSolutionName != null)
                {
                    // A solution was closed and a file has been opened outside of a solution so clear the
                    // cache and use the global dictionaries.
                    WpfTextBox.WpfTextBoxSpellChecker.ClearCache();
                    GlobalDictionary.ClearDictionaryCache();
                    LastSolutionName = null;
                }
            }
Exemplo n.º 12
0
        //=====================================================================

        /// <summary>
        /// Generate the configuration to use when spell checking the given text buffer
        /// </summary>
        /// <param name="buffer">The text buffer for which to generate a configuration</param>
        /// <returns>The generated configuration to use</returns>
        /// <remarks>The configuration is a merger of the global settings plus any solution, project, folder, and
        /// file settings related to the text buffer.</remarks>
        private SpellCheckerConfiguration GenerateConfiguration(ITextBuffer buffer)
        {
            ProjectItem projectItem, fileItem;
            string      bufferFilename, filename, projectPath;

            // Start with the global configuration
            var config = new SpellCheckerConfiguration();

            try
            {
                config.Load(SpellingConfigurationFile.GlobalConfigurationFilename);

                var dte2 = (globalServiceProvider == null) ? null :
                           globalServiceProvider.GetService(typeof(SDTE)) as DTE2;

                if (dte2 != null && dte2.Solution != null && !String.IsNullOrWhiteSpace(dte2.Solution.FullName))
                {
                    var solution = dte2.Solution;

                    // Clear the global dictionary cache when a change in solution is detected
                    if (lastSolutionName == null || !lastSolutionName.Equals(solution.FullName,
                                                                             StringComparison.OrdinalIgnoreCase))
                    {
                        GlobalDictionary.ClearDictionaryCache();
                        lastSolutionName = solution.FullName;
                    }

                    // See if there is a solution configuration
                    filename    = solution.FullName + ".vsspell";
                    projectItem = solution.FindProjectItem(filename);

                    if (projectItem != null)
                    {
                        config.Load(filename);
                    }

                    // Find the project item for the file we are opening
                    bufferFilename = buffer.GetFilename();
                    projectItem    = (bufferFilename != null) ? solution.FindProjectItem(bufferFilename) : null;

                    if (projectItem != null)
                    {
                        fileItem = projectItem;

                        // If we have a project (we should), see if it has settings
                        if (projectItem.ContainingProject != null &&
                            !String.IsNullOrWhiteSpace(projectItem.ContainingProject.FullName))
                        {
                            filename    = projectItem.ContainingProject.FullName + ".vsspell";
                            projectItem = solution.FindProjectItem(filename);

                            if (projectItem != null)
                            {
                                config.Load(filename);
                            }

                            // Get the full path based on the project.  The buffer filename will refer to the actual
                            // path which may be to a linked file outside the project's folder structure.
                            projectPath = Path.GetDirectoryName(filename);
                            filename    = Path.GetDirectoryName((string)fileItem.Properties.Item("FullPath").Value);

                            // Search for folder-specific configuration files
                            if (filename.StartsWith(projectPath, StringComparison.OrdinalIgnoreCase))
                            {
                                // Then check subfolders.  No need to check the root folder as the project
                                // settings cover it.
                                if (filename.Length > projectPath.Length)
                                {
                                    foreach (string folder in filename.Substring(projectPath.Length + 1).Split('\\'))
                                    {
                                        projectPath = Path.Combine(projectPath, folder);
                                        filename    = Path.Combine(projectPath, folder + ".vsspell");
                                        projectItem = solution.FindProjectItem(filename);

                                        if (projectItem != null)
                                        {
                                            config.Load(filename);
                                        }
                                    }
                                }
                            }

                            // If the item looks like a dependent file item, look for a settings file related to
                            // the parent file item.
                            if (fileItem.Collection != null && fileItem.Collection.Parent != null)
                            {
                                projectItem = fileItem.Collection.Parent as ProjectItem;

                                if (projectItem != null && projectItem.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFile)
                                {
                                    filename    = (string)projectItem.Properties.Item("FullPath").Value + ".vsspell";
                                    projectItem = solution.FindProjectItem(filename);

                                    if (projectItem != null)
                                    {
                                        config.Load(filename);
                                    }
                                }
                            }

                            // And finally, look for file-specific settings for the item itself
                            filename    = (string)fileItem.Properties.Item("FullPath").Value + ".vsspell";
                            projectItem = solution.FindProjectItem(filename);

                            if (projectItem != null)
                            {
                                config.Load(filename);
                            }
                        }
                    }

                    if (bufferFilename != null && config.DetermineResourceFileLanguageFromName &&
                        Path.GetExtension(bufferFilename).Equals(".resx", StringComparison.OrdinalIgnoreCase))
                    {
                        // Localized resource files are expected to have filenames in the format
                        // BaseName.Language.resx (i.e. LocalizedForm.de-DE.resx).
                        bufferFilename = Path.GetExtension(Path.GetFileNameWithoutExtension(bufferFilename));

                        if (bufferFilename.Length > 1)
                        {
                            bufferFilename = bufferFilename.Substring(1);

                            SpellCheckerDictionary match;

                            if (SpellCheckerDictionary.AvailableDictionaries(
                                    config.AdditionalDictionaryFolders).TryGetValue(bufferFilename, out match))
                            {
                                config.DefaultLanguage = match.Culture;
                            }
                        }
                    }
                }
                else
                if (lastSolutionName != null)
                {
                    // A solution was closed and a file has been opened outside of a solution so clear the
                    // cache and use the global dictionaries.
                    GlobalDictionary.ClearDictionaryCache();
                    lastSolutionName = null;
                }
            }
            catch (Exception ex)
            {
                // Ignore errors, we just won't load the configurations after the point of failure
                System.Diagnostics.Debug.WriteLine(ex);
            }

            return(config);
        }