/// <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> /// This is used to determine the containing project and settings filename when adding a new spell /// checker configuration file. /// </summary> /// <param name="containingProject">On return, this contains the containing project or null if adding /// a solution configuration file.</param> /// <param name="settingsFilename">On return, this contains the name of the settings file to be added</param> /// <returns>True if a settings file can be added for the item selected in the Solution Explorer window /// or false if not.</returns> private static bool DetermineContainingProjectAndSettingsFile(out Project containingProject, out string settingsFilename) { ThreadHelper.ThrowIfNotOnUIThread(); string folderName = null; containingProject = null; settingsFilename = null; // Only add if a single file is selected if (!(Package.GetGlobalService(typeof(SDTE)) is DTE2 dte2) || dte2.SelectedItems.Count != 1) { return(false); } SelectedItem item = dte2.SelectedItems.Item(1); if (item.Project != null && item.Project.Kind != EnvDTE.Constants.vsProjectKindSolutionItems && item.Project.Kind != EnvDTE.Constants.vsProjectKindUnmodeled && item.Project.Kind != EnvDTE.Constants.vsProjectKindMisc) { string path = null; // Looks like a project. Not all of them implement properties though. if (!String.IsNullOrWhiteSpace(item.Project.FullName) && item.Project.FullName.EndsWith( "proj", StringComparison.OrdinalIgnoreCase)) { path = item.Project.FullName; } if (path == null && item.Project.Properties != null) { Property fullPath; try { fullPath = item.Project.Properties.Item("FullPath"); } catch { // C++ projects use a different property name and throw an exception above try { fullPath = item.Project.Properties.Item("ProjectFile"); } catch { // If that fails, give up fullPath = null; } } if (fullPath != null && fullPath.Value != null) { path = (string)fullPath.Value; } } if (!String.IsNullOrWhiteSpace(path)) { #pragma warning disable VSTHRD010 var project = dte2.Solution.EnumerateProjects().FirstOrDefault(p => p.Name == item.Name); #pragma warning restore VSTHRD010 if (project != null) { containingProject = project; settingsFilename = project.FullName; // Website projects are named after the folder rather than a file if (settingsFilename.Length > 1 && settingsFilename[settingsFilename.Length - 1] == '\\') { folderName = settingsFilename; settingsFilename += item.Name; } } } } else if (item.ProjectItem == null || item.ProjectItem.ContainingProject == null) { // Looks like a solution if (Path.GetFileNameWithoutExtension(dte2.Solution.FullName) == item.Name) { settingsFilename = dte2.Solution.FullName; } } else if (item.ProjectItem.Properties != null) { // Looks like a folder or file item Property fullPath; try { fullPath = item.ProjectItem.Properties.Item("FullPath"); } catch { fullPath = null; } if (fullPath != null && fullPath.Value != null) { string path = (string)fullPath.Value; if (!String.IsNullOrWhiteSpace(path)) { containingProject = item.ProjectItem.ContainingProject; // Folder items have a trailing backslash in some project systems, others don't. // We'll put the configuration file in the folder using its name as the filename. if (path[path.Length - 1] == '\\' || (!File.Exists(path) && Directory.Exists(path))) { if (path[path.Length - 1] != '\\') { path += @"\"; } folderName = path; settingsFilename = path + item.Name; } else { settingsFilename = path; } } } } else if (item.ProjectItem.Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems) { // Looks like a solution item settingsFilename = item.ProjectItem.get_FileNames(1); } if (settingsFilename != null) { if (settingsFilename.EndsWith(".vsspell", StringComparison.OrdinalIgnoreCase) || ((folderName == null && !File.Exists(settingsFilename)) || (folderName != null && !Directory.Exists(folderName)))) { settingsFilename = null; } else if (folderName == null) { if (SpellCheckFileInfo.IsBinaryFile(settingsFilename)) { settingsFilename = null; } else { settingsFilename += ".vsspell"; } } else { settingsFilename += ".vsspell"; } } return(settingsFilename != null); }
//===================================================================== /// <summary> /// This is used to determine the containing project and settings filename when adding a new spell /// checker configuration file. /// </summary> /// <param name="containingProject">On return, this contains the containing project or null if adding /// a solution configuration file.</param> /// <param name="settingsFilename">On return, this contains the name of the settings file to be added</param> /// <returns>True if a settings file can be added for the item selected in the Solution Explorer window /// or false if not.</returns> private static bool DetermineContainingProjectAndSettingsFile(out Project containingProject, out string settingsFilename) { string folderName = null; containingProject = null; settingsFilename = null; var dte2 = Utility.GetServiceFromPackage <DTE2, SDTE>(true); // Only add if a single file is selected if (dte2 == null || dte2.SelectedItems.Count != 1) { return(false); } SelectedItem item = dte2.SelectedItems.Item(1); if (item.Project != null && item.Project.Kind != EnvDTE.Constants.vsProjectKindSolutionItems && item.Project.Kind != EnvDTE.Constants.vsProjectKindUnmodeled && item.Project.Kind != EnvDTE.Constants.vsProjectKindMisc) { // Looks like a project Property fullPath = item.Project.Properties.Item("FullPath"); if (fullPath != null && fullPath.Value != null) { string path = (string)fullPath.Value; if (!String.IsNullOrWhiteSpace(path)) { var project = dte2.Solution.EnumerateProjects().FirstOrDefault(p => p.Name == item.Name); if (project != null) { containingProject = project; settingsFilename = project.FullName; // Website projects are named after the folder rather than a file if (settingsFilename.Length > 1 && settingsFilename[settingsFilename.Length - 1] == '\\') { folderName = settingsFilename; settingsFilename += item.Name; } } } } } else if (item.ProjectItem == null || item.ProjectItem.ContainingProject == null) { // Looks like a solution if (Path.GetFileNameWithoutExtension(dte2.Solution.FullName) == item.Name) { settingsFilename = dte2.Solution.FullName; } } else if (item.ProjectItem.Properties != null) { // Looks like a folder or file item Property fullPath = item.ProjectItem.Properties.Item("FullPath"); if (fullPath != null && fullPath.Value != null) { string path = (string)fullPath.Value; if (!String.IsNullOrWhiteSpace(path)) { containingProject = item.ProjectItem.ContainingProject; // Folder items have a trailing backslash. We'll put the configuration file in // the folder using its name as the filename. if (path[path.Length - 1] == '\\') { folderName = path; settingsFilename = path + item.Name; } else { settingsFilename = path; } } } } else if (item.ProjectItem.Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems) { // Looks like a solution item settingsFilename = item.ProjectItem.get_FileNames(1); } if (settingsFilename != null) { if (settingsFilename.EndsWith(".vsspell", StringComparison.OrdinalIgnoreCase) || ((folderName == null && !File.Exists(settingsFilename)) || (folderName != null && !Directory.Exists(folderName)))) { settingsFilename = null; } else if (folderName == null) { if (SpellCheckFileInfo.IsBinaryFile(settingsFilename)) { settingsFilename = null; } else { settingsFilename += ".vsspell"; } } else { settingsFilename += ".vsspell"; } } return(settingsFilename != null); }