/// <summary> /// This is used to generate the configuration for the instance /// </summary> /// <returns>The configuration to use or null if the file should not be spell checked (disabled or not a /// type of file that can be spell checked such as a binary file).</returns> public SpellCheckerConfiguration GenerateConfiguration(IEnumerable <string> codeAnalysisFiles) { var config = new SpellCheckerConfiguration(); try { foreach (var c in this.ConfigurationFiles) { config.Load(c.Value); } // Merge any code analysis dictionary settings if (codeAnalysisFiles != null) { foreach (string cad in codeAnalysisFiles) { if (File.Exists(cad)) { config.ImportCodeAnalysisDictionary(cad); } } } // If wanted, set the language based on the resource filename if (config.DetermineResourceFileLanguageFromName && Path.GetExtension(this.Filename).Equals(".resx", StringComparison.OrdinalIgnoreCase)) { // Localized resource files are expected to have filenames in the format // BaseName.Language.resx (i.e. LocalizedForm.de-DE.resx). string ext = Path.GetExtension(Path.GetFileNameWithoutExtension(this.Filename)); if (ext.Length > 1) { ext = ext.Substring(1); SpellCheckerDictionary match; if (SpellCheckerDictionary.AvailableDictionaries( config.AdditionalDictionaryFolders).TryGetValue(ext, 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); } } } } catch (Exception ex) { // Ignore errors, we just won't load the configurations after the point of failure System.Diagnostics.Debug.WriteLine(ex); } return((!config.IncludeInProjectSpellCheck || config.ShouldExcludeFile(this.CanonicalName) || IsBinaryFile(this.CanonicalName)) ? null : config); }
//===================================================================== /// <summary> /// This loads the list of available dictionary languages /// </summary> /// <remarks>This will find all dictionaries in the global configuration folder as well as any additional /// folders specified in this configuration file.</remarks> private void LoadAvailableLanguages() { List <SpellCheckerDictionary> availableDictionaries = new List <SpellCheckerDictionary>(); if (cboDefaultLanguage.Items.Count != 0) { defaultLang = ((SpellCheckerDictionary)cboDefaultLanguage.SelectedItem).Culture; } if (!isGlobal) { availableDictionaries.Add(new SpellCheckerDictionary(CultureInfo.InvariantCulture, null, null, null)); } List <string> additionalFolders = new List <string>(); // Fully qualify relative paths with the configuration file path foreach (string folder in lbAdditionalFolders.Items.OfType <string>()) { if (Path.IsPathRooted(folder)) { additionalFolders.Add(folder); } else { additionalFolders.Add(Path.GetFullPath(Path.Combine(configFilePath, folder))); } } foreach (var lang in SpellCheckerDictionary.AvailableDictionaries(additionalFolders).Values.OrderBy( d => d.ToString())) { availableDictionaries.Add(lang); } cboDefaultLanguage.ItemsSource = availableDictionaries; if (defaultLang != null) { var match = cboDefaultLanguage.Items.OfType <SpellCheckerDictionary>().FirstOrDefault( d => d.Culture.Name == defaultLang.Name); if (match != null) { cboDefaultLanguage.SelectedItem = match; } else { cboDefaultLanguage.SelectedIndex = 0; } } else { cboDefaultLanguage.SelectedIndex = 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); }
//===================================================================== /// <summary> /// This loads the list of available dictionary languages /// </summary> /// <remarks>This will find all dictionaries in the global configuration folder as well as any additional /// folders specified in this configuration file.</remarks> private void LoadAvailableLanguages() { SpellCheckerDictionary match = null; List <SpellCheckerDictionary> availableDictionaries = new List <SpellCheckerDictionary>(); CultureInfo defaultLang = null; if (cboAvailableLanguages.Items.Count != 0) { defaultLang = ((SpellCheckerDictionary)cboAvailableLanguages.SelectedItem).Culture; selectedLanguages.Clear(); selectedLanguages.AddRange(lbSelectedLanguages.Items.Cast <SpellCheckerDictionary>().Select( d => d.Culture.Name)); } cboAvailableLanguages.ItemsSource = null; if (!isGlobal) { availableDictionaries.Add(new SpellCheckerDictionary(CultureInfo.InvariantCulture, null, null, null, false)); } List <string> additionalFolders = new List <string>(); // Include inherited additional folders from parent configurations. This allows for consistent // user dictionary content across all configuration files within a solution and/or project if (chkInheritAdditionalFolders.IsChecked.Value) { #pragma warning disable VSTHRD010 var parentConfig = this.GenerateParentConfiguration(); #pragma warning restore VSTHRD010 additionalFolders.AddRange(parentConfig.AdditionalDictionaryFolders); } // Fully qualify relative paths with the configuration file path foreach (string folder in lbAdditionalFolders.Items.Cast <string>()) { if (folder.IndexOf('%') != -1 || Path.IsPathRooted(folder)) { additionalFolders.Add(folder); } else { additionalFolders.Add(Path.GetFullPath(Path.Combine(configFilePath, folder))); } } foreach (var lang in SpellCheckerDictionary.AvailableDictionaries( additionalFolders.Distinct()).Values.OrderBy(d => d.ToString())) { availableDictionaries.Add(lang); } cboAvailableLanguages.ItemsSource = availableDictionaries; // Add selected languages first if (selectedLanguages.Count != 0) { lbSelectedLanguages.Items.Clear(); foreach (string language in selectedLanguages) { match = availableDictionaries.FirstOrDefault(d => d.Culture.Name.Equals(language, StringComparison.OrdinalIgnoreCase)); if (match != null) { lbSelectedLanguages.Items.Add(match); } } } // Then set the default language selection for the user dictionary if (defaultLang != null) { match = availableDictionaries.FirstOrDefault(d => d.Culture.Name == defaultLang.Name); if (match != null) { cboAvailableLanguages.SelectedItem = match; } else { cboAvailableLanguages.SelectedIndex = 0; } } else { if (lbSelectedLanguages.Items.Count != 0) { var primary = (SpellCheckerDictionary)lbSelectedLanguages.Items[0]; match = availableDictionaries.FirstOrDefault(d => d.Culture.Name == primary.Culture.Name); } else if (isGlobal) { match = availableDictionaries.FirstOrDefault(d => d.Culture.Name == "en-US"); } if (match != null) { cboAvailableLanguages.SelectedItem = match; } else if (cboAvailableLanguages.Items.Count != 0) { cboAvailableLanguages.SelectedIndex = 0; } } lbSelectedLanguages_SelectionChanged(this, null); }
/// <summary> /// This is used to initialize the global dictionary /// </summary> /// <param name="additionalDictionaryFolders">An enumerable list of additional folders to search for /// other dictionaries.</param> private void InitializeDictionary(IEnumerable <string> additionalDictionaryFolders) { string affixFile, userWordsFile, dllPath = Path.Combine(Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location), "NHunspell"); // Look for all available dictionaries and get the one for the requested culture var dictionaries = SpellCheckerDictionary.AvailableDictionaries(additionalDictionaryFolders); if (dictionaries.TryGetValue(this.Culture.Name, out SpellCheckerDictionary userDictionary)) { affixFile = userDictionary.AffixFilePath; dictionaryFile = userDictionary.DictionaryFilePath; userWordsFile = userDictionary.UserDictionaryFilePath; } else { affixFile = dictionaryFile = userWordsFile = null; } // If not found, default to the US 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 US 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"); } lock (syncRoot) { if (spellEngine != null && !isDisposed) { spellEngine.AddLanguage(new LanguageConfig { LanguageCode = this.Culture.Name, HunspellAffFile = affixFile, HunspellDictFile = dictionaryFile }); spellFactory = spellEngine[this.Culture.Name]; if (String.IsNullOrWhiteSpace(userWordsFile)) { dictionaryWordsFile = Path.Combine(SpellingConfigurationFile.GlobalConfigurationFilePath, this.Culture.Name + "_User.dic"); } else { dictionaryWordsFile = userWordsFile; } this.LoadUserDictionaryFile(); #if DEBUG // Add an artificial delay to allow for testing readiness checks //Thread.Sleep(5000); #endif this.IsInitialized = true; } } }
//===================================================================== /// <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> /// 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); }