/// <summary> /// Get the dictionary for the specified buffer /// </summary> /// <param name="buffer">The buffer for which to get a dictionary</param> /// <returns>The spelling dictionary for the buffer or null if one is not provided</returns> public SpellingDictionary GetDictionary(ITextBuffer buffer) { SpellingDictionary service = null; if (buffer != null && !buffer.Properties.TryGetProperty(typeof(SpellingDictionary), out service)) { // Get the configuration and create the dictionary based on the configuration var config = this.GetConfiguration(buffer); if (config != null) { // Create or get the existing global dictionary for the default language var globalDictionary = GlobalDictionary.CreateGlobalDictionary(config.DefaultLanguage, globalServiceProvider, config.AdditionalDictionaryFolders, config.RecognizedWords); if (globalDictionary != null) { service = new SpellingDictionary(globalDictionary, config.IgnoredWords); buffer.Properties[typeof(SpellingDictionary)] = service; } } } return(service); }
//===================================================================== /// <inheritdoc /> public ISpellingDictionary GetDictionary(ITextBuffer buffer) { ISpellingDictionary service = null; if (buffer.Properties.TryGetProperty(typeof(SpellingDictionaryService), out service)) { return(service); } List <ISpellingDictionary> bufferSpecificDictionaries = new List <ISpellingDictionary>(); foreach (var provider in bufferSpecificDictionaryProviders) { var dictionary = provider.Value.GetDictionary(buffer); if (dictionary != null) { bufferSpecificDictionaries.Add(dictionary); } } // Create or get the existing global dictionary for the default language var globalDictionary = GlobalDictionary.CreateGlobalDictionary(null); if (globalDictionary != null) { service = new SpellingDictionaryService(bufferSpecificDictionaries, globalDictionary); buffer.Properties[typeof(SpellingDictionaryService)] = service; } return(service); }
/// <summary> /// Get the dictionary for the specified buffer /// </summary> /// <param name="buffer">The buffer for which to get a dictionary</param> /// <returns>The spelling dictionary for the buffer or null if one is not provided</returns> public SpellingDictionary GetDictionary(ITextBuffer buffer) { SpellingDictionary service = null; if (buffer != null && !buffer.Properties.TryGetProperty(typeof(SpellingDictionary), out service)) { // Get the configuration and create the dictionary based on the configuration var config = this.GetConfiguration(buffer); if (config != null) { // Create a dictionary for each configuration dictionary language ignoring any that are // invalid and duplicates caused by missing languages which return the en-US dictionary. var globalDictionaries = config.DictionaryLanguages.Select(l => GlobalDictionary.CreateGlobalDictionary(l, globalServiceProvider, config.AdditionalDictionaryFolders, config.RecognizedWords)).Where( d => d != null).Distinct().ToList(); if (globalDictionaries.Any()) { service = new SpellingDictionary(globalDictionaries, config.IgnoredWords); buffer.Properties[typeof(SpellingDictionary)] = service; } } } return(service); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="globalDictionary">The global dictionary</param> /// <param name="ignoredWords">An optional enumerable list of ignored words</param> public SpellingDictionary(GlobalDictionary globalDictionary, IEnumerable <string> ignoredWords) { this.globalDictionary = globalDictionary; this.ignoredWords = (ignoredWords ?? Enumerable.Empty <string>()); // Register to receive events when the global dictionary is updated globalDictionary.RegisterSpellingDictionaryService(this); }
//===================================================================== /// <summary> /// Create a global dictionary for the specified culture /// </summary> /// <param name="culture">The language to use for the dictionary.</param> /// <param name="additionalDictionaryFolders">An enumerable list of additional folders to search for /// other dictionaries.</param> /// <param name="recognizedWords">An optional list of recognized words that will be added to the /// dictionary (i.e. from a code analysis dictionary).</param> /// <returns>The global dictionary to use or null if one could not be created.</returns> public static GlobalDictionary CreateGlobalDictionary(CultureInfo culture, IEnumerable <string> additionalDictionaryFolders, IEnumerable <string> recognizedWords) { GlobalDictionary globalDictionary = null; try { if (globalDictionaries == null) { globalDictionaries = new Dictionary <string, GlobalDictionary>(); } if (spellEngine == null) { Hunspell.NativeDllPath = Path.Combine(Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location), "NHunspell"); spellEngine = new SpellEngine(); } // The configuration editor should disallow creating a configuration without at least one // language but if someone edits the file manually, they could remove them all. If that // happens, just use the English-US dictionary. if (culture == null) { culture = new CultureInfo("en-US"); } // If not already loaded, create the dictionary and the thread-safe spell factory instance for // the given culture. if (!globalDictionaries.TryGetValue(culture.Name, out globalDictionary)) { globalDictionary = new GlobalDictionary(culture, recognizedWords); // Initialize the dictionaries asynchronously. We don't care about the result here. _ = Task.Run(() => globalDictionary.InitializeDictionary(additionalDictionaryFolders)); globalDictionaries.Add(culture.Name, globalDictionary); } else { // Add recognized words that are not already there globalDictionary.AddRecognizedWords(recognizedWords); } } catch (Exception ex) { // Ignore exceptions. Not much we can do, we just won't spell check anything in this language. System.Diagnostics.Debug.WriteLine(ex); } return(globalDictionary); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="bufferSpecificDictionaries">A list of buffer-specific dictionaries</param> /// <param name="globalDictionary">The global dictionary</param> public SpellingDictionaryService(IList <ISpellingDictionary> bufferSpecificDictionaries, GlobalDictionary globalDictionary) { this.globalDictionary = globalDictionary; this.bufferSpecificDictionaries = bufferSpecificDictionaries; // TODO: This never gets disconnected and would probably keep this instance alive, right? Probably // should switch to something like RegisterSpellingDictionaryService used by global dictionary. // Perhaps make that method part of the ISpellingDictionary interface? Need to test it once a // buffer-specific class is actually implemented. foreach (var dictionary in bufferSpecificDictionaries) { dictionary.DictionaryUpdated += this.BufferSpecificDictionaryUpdated; } // Register to receive events when the global dictionary is updated globalDictionary.RegisterSpellingDictionaryService(this); }
/// <summary> /// Add the given word to the dictionary so that it will no longer show up as an incorrect spelling /// </summary> /// <param name="word">The word to add to the dictionary.</param> /// <param name="culture">The culture of the dictionary to which the word is added or null to add it to /// the first dictionary.</param> /// <returns><c>true</c> if the word was successfully added to the dictionary, even if it was already in /// the dictionary.</returns> public bool AddWordToDictionary(string word, CultureInfo culture) { GlobalDictionary dictionary = null; if (String.IsNullOrWhiteSpace(word)) { return(false); } if (culture != null) { dictionary = this.Dictionaries.FirstOrDefault(d => d.Culture == culture); } if (dictionary == null) { dictionary = this.Dictionaries.First(); } return(this.ShouldIgnoreWord(word) || dictionary.AddWordToDictionary(word)); }
//===================================================================== /// <summary> /// This is used to clear the global dictionary cache whenever a solution is closed /// </summary> /// <remarks>The spelling service factory also contains code to clear the dictionary cache when it /// detects a change in solution. This package will not load unless a configuration is edited. This is /// needed to consistently clear the cache if editing configurations in different solutions without /// opening any spell checked files.</remarks> private static void solutionEvents_AfterClosing() { WpfTextBox.WpfTextBoxSpellChecker.ClearCache(); GlobalDictionary.ClearDictionaryCache(); }
//===================================================================== /// <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); }
//===================================================================== /// <summary> /// Create a global dictionary for the specified culture /// </summary> /// <param name="culture">The language to use for the dictionary.</param> /// <param name="serviceProvider">A service provider used to interact with the solution/project</param> /// <param name="additionalDictionaryFolders">An enumerable list of additional folders to search for /// other dictionaries.</param> /// <param name="recognizedWords">An optional list of recognized words that will be added to the /// dictionary (i.e. from a code analysis dictionary).</param> /// <returns>The global dictionary to use or null if one could not be created.</returns> public static GlobalDictionary CreateGlobalDictionary(CultureInfo culture, IServiceProvider serviceProvider, IEnumerable <string> additionalDictionaryFolders, IEnumerable <string> recognizedWords) { GlobalDictionary globalDictionary = null; string affixFile, dictionaryFile, userWordsFile; try { // The configuration editor should disallow creating a configuration without at least one // language but if someone edits the file manually, they could remove them all. If that // happens, just use the English-US dictionary. if (culture == null) { culture = new CultureInfo("en-US"); } if (globalDictionaries == null) { globalDictionaries = new Dictionary <string, GlobalDictionary>(); } // If not already loaded, create the dictionary and the thread-safe spell factory instance for // the given culture. if (!globalDictionaries.ContainsKey(culture.Name)) { string dllPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "NHunspell"); if (spellEngine == null) { Hunspell.NativeDllPath = dllPath; spellEngine = new SpellEngine(); } // Look for all available dictionaries and get the one for the requested culture var dictionaries = SpellCheckerDictionary.AvailableDictionaries(additionalDictionaryFolders); SpellCheckerDictionary userDictionary; if (dictionaries.TryGetValue(culture.Name, out userDictionary)) { affixFile = userDictionary.AffixFilePath; dictionaryFile = userDictionary.DictionaryFilePath; userWordsFile = userDictionary.UserDictionaryFilePath; } else { affixFile = dictionaryFile = userWordsFile = null; } // If not found, default to the English dictionary supplied with the package. This can at // least clue us in that it didn't find the language-specific dictionary when the suggestions // are in English. if (affixFile == null || dictionaryFile == null || !File.Exists(affixFile) || !File.Exists(dictionaryFile)) { affixFile = Path.Combine(dllPath, "en_US.aff"); dictionaryFile = Path.ChangeExtension(affixFile, ".dic"); userWordsFile = Path.Combine(SpellingConfigurationFile.GlobalConfigurationFilePath, "en-US_User.dic"); } spellEngine.AddLanguage(new LanguageConfig { LanguageCode = culture.Name, HunspellAffFile = affixFile, HunspellDictFile = dictionaryFile }); globalDictionaries.Add(culture.Name, new GlobalDictionary(culture, spellEngine[culture.Name], dictionaryFile, userWordsFile, serviceProvider)); } globalDictionary = globalDictionaries[culture.Name]; // Add recognized words that are not already there globalDictionary.AddRecognizedWords(recognizedWords); } catch (Exception ex) { // Ignore exceptions. Not much we can do, we just won't spell check anything. System.Diagnostics.Debug.WriteLine(ex); } return(globalDictionary); }
/// <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; } }
//===================================================================== /// <summary> /// Create a global dictionary for the specified culture /// </summary> /// <param name="culture">The language to use for the dictionary</param> /// <returns>The spell factory to use or null if one could not be created</returns> public static GlobalDictionary CreateGlobalDictionary(CultureInfo culture) { GlobalDictionary globalDictionary = null; string affixFile, dictionaryFile; try { if (globalDictionaries == null) { globalDictionaries = new Dictionary <string, GlobalDictionary>(); } // If no culture is specified, use the default culture if (culture == null) { culture = SpellCheckerConfiguration.DefaultLanguage; } // If not already loaded, create the dictionary and the thread-safe spell factory instance for // the given culture. if (!globalDictionaries.ContainsKey(culture.Name)) { string dllPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "NHunspell"); if (spellEngine == null) { Hunspell.NativeDllPath = dllPath; spellEngine = new SpellEngine(); } // Look in the configuration folder first for user-supplied dictionaries affixFile = Path.Combine(SpellCheckerConfiguration.ConfigurationFilePath, culture.Name + ".aff"); dictionaryFile = Path.ChangeExtension(affixFile, ".dic"); // Dictionary file names may use a dash or an underscore as the separator. Try both ways // in case they aren't named consistently which does happen. if (!File.Exists(affixFile)) { affixFile = Path.Combine(SpellCheckerConfiguration.ConfigurationFilePath, culture.Name.Replace('-', '_') + ".aff"); } if (!File.Exists(dictionaryFile)) { dictionaryFile = Path.Combine(SpellCheckerConfiguration.ConfigurationFilePath, culture.Name.Replace('-', '_') + ".dic"); } // If not found, default to the English dictionary supplied with the package. This can at // least clue us in that it didn't find the language-specific dictionary when the suggestions // are in English. if (!File.Exists(affixFile) || !File.Exists(dictionaryFile)) { affixFile = Path.Combine(dllPath, "en_US.aff"); dictionaryFile = Path.ChangeExtension(affixFile, ".dic"); } spellEngine.AddLanguage(new LanguageConfig { LanguageCode = culture.Name, HunspellAffFile = affixFile, HunspellDictFile = dictionaryFile }); globalDictionaries.Add(culture.Name, new GlobalDictionary(culture, spellEngine[culture.Name])); } globalDictionary = globalDictionaries[culture.Name]; } catch (Exception ex) { // Ignore exceptions. Not much we can do, we'll just not spell check anything. System.Diagnostics.Debug.WriteLine(ex); } return(globalDictionary); }
//===================================================================== /// <summary> /// This is used to clear the global dictionary cache whenever a solution is closed /// </summary> /// <remarks>The spelling service factory also contains code to clear the dictionary cache when it /// detects a change in solution. This package will not load unless a configuration is edited. This is /// needed to consistently clear the cache if editing configurations in different solutions without /// opening any spell checked files.</remarks> void solutionEvents_AfterClosing() { GlobalDictionary.ClearDictionaryCache(); }
//===================================================================== /// <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); }